feat: add event approval workflow with admin UI
Non-admin event submissions now land in a "proposed" queue that LNbits
admins review before the event becomes ticketable and publicly listed.
- m008 adds events.events.status (proposed/approved/rejected); m010 seeds
an events.settings singleton row with the auto_approve toggle.
- Models: Event/CreateEvent.status, EventsSettings, optional date fields
with sensible defaults (closing_date defaults to event_end_date which
defaults to event_start_date), PublicEvent.status surfaces the workflow
state on the public endpoint.
- crud: get_all/public/pending_events for the admin views; get/update_settings
for the auto_approve toggle; create_event auto-fills missing date defaults.
- views_api:
* POST /api/v1/events accepts wallet invoice keys so anyone can submit;
handler stamps status="proposed" for non-admins when auto_approve is off
* /public, /all, /pending, /settings (GET+PUT), /{id}/{approve,reject},
/{id}/tickets endpoints; literal-prefix routes declared before /{event_id}
so FastAPI matches them correctly
* Public GET /{event_id} bypasses sold-out / closing-window gates for
proposed/rejected events and returns the trimmed PublicEvent so the SFC
can render a "pending approval" banner
* POST /tickets/{event_id} rejects when event.status != "approved"
- Frontend: index.vue gains an admin Settings card, Pending Approvals list,
status badge column and approve/reject row actions, plus an All Users'
Events admin table; index.js gains the data + methods + an isAdmin probe
via GET /events/all; display.vue shows pending/rejected banners and
hides the Buy Ticket form unless status === "approved".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dfabcb8f54
commit
c7e95c5452
6 changed files with 494 additions and 51 deletions
|
|
@ -6,6 +6,12 @@ window.PageEvents = {
|
|||
tickets: [],
|
||||
resendingTicketEmails: [],
|
||||
currencies: [],
|
||||
pendingEvents: [],
|
||||
allUserEvents: [],
|
||||
isAdmin: false,
|
||||
settings: {
|
||||
auto_approve: false
|
||||
},
|
||||
eventsTable: {
|
||||
columns: [
|
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||
|
|
@ -66,7 +72,8 @@ window.PageEvents = {
|
|||
field: 'sold'
|
||||
},
|
||||
{name: 'info', align: 'left', label: 'Info', field: 'info'},
|
||||
{name: 'banner', align: 'left', label: 'Banner', field: 'banner'}
|
||||
{name: 'banner', align: 'left', label: 'Banner', field: 'banner'},
|
||||
{name: 'status', align: 'left', label: 'Status', field: 'status'}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
|
|
@ -189,6 +196,84 @@ window.PageEvents = {
|
|||
this.events = response.data
|
||||
this.checkCanceledEvents()
|
||||
})
|
||||
|
||||
// Admin probe: a 200 from /all means we're an LNbits admin.
|
||||
LNbits.api
|
||||
.request('GET', '/events/api/v1/events/all')
|
||||
.then(response => {
|
||||
this.isAdmin = true
|
||||
const ownWalletIds = this.g.user.wallets.map(w => w.id)
|
||||
this.allUserEvents = response.data.filter(
|
||||
e => !ownWalletIds.includes(e.wallet)
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
this.isAdmin = false
|
||||
this.allUserEvents = []
|
||||
})
|
||||
},
|
||||
getSettings() {
|
||||
LNbits.api
|
||||
.request('GET', '/events/api/v1/events/settings')
|
||||
.then(response => {
|
||||
this.settings = response.data
|
||||
})
|
||||
.catch(() => {
|
||||
// Not admin or settings unavailable; keep defaults.
|
||||
})
|
||||
},
|
||||
saveSettings() {
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/events/api/v1/events/settings',
|
||||
null,
|
||||
this.settings
|
||||
)
|
||||
.then(() => {
|
||||
Quasar.Notify.create({type: 'positive', message: 'Settings saved'})
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
},
|
||||
getPendingEvents() {
|
||||
LNbits.api
|
||||
.request('GET', '/events/api/v1/events/pending')
|
||||
.then(response => {
|
||||
this.pendingEvents = response.data
|
||||
})
|
||||
.catch(() => {
|
||||
this.pendingEvents = []
|
||||
})
|
||||
},
|
||||
approveEvent(eventId) {
|
||||
LNbits.utils.confirmDialog('Approve this event?').onOk(() => {
|
||||
LNbits.api
|
||||
.request('PUT', '/events/api/v1/events/' + eventId + '/approve')
|
||||
.then(() => {
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Event approved'
|
||||
})
|
||||
this.getEvents()
|
||||
this.getPendingEvents()
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
})
|
||||
},
|
||||
rejectEvent(eventId) {
|
||||
LNbits.utils.confirmDialog('Reject this event?').onOk(() => {
|
||||
LNbits.api
|
||||
.request('PUT', '/events/api/v1/events/' + eventId + '/reject')
|
||||
.then(() => {
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Event rejected'
|
||||
})
|
||||
this.getEvents()
|
||||
this.getPendingEvents()
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
})
|
||||
},
|
||||
sendEventData() {
|
||||
const wallet = _.findWhere(this.g.user.wallets, {
|
||||
|
|
@ -333,6 +418,8 @@ window.PageEvents = {
|
|||
if (this.g.user.wallets.length) {
|
||||
this.getTickets()
|
||||
this.getEvents()
|
||||
this.getSettings()
|
||||
this.getPendingEvents()
|
||||
if (this.g.allowedCurrencies && this.g.allowedCurrencies.length > 0) {
|
||||
this.currencies = ['sats', ...this.g.allowedCurrencies]
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue