feat: add resend email button to ticket list (#51)
- resending only possible when ticket is paid.
This commit is contained in:
parent
6768b78c6f
commit
4bf867eef0
5 changed files with 111 additions and 13 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "events",
|
||||
"version": "1.3.0",
|
||||
"version": "1.6.1",
|
||||
"name": "Events",
|
||||
"repo": "https://github.com/lnbits/events",
|
||||
"short_description": "Sell and register event tickets",
|
||||
|
|
|
|||
42
services.py
42
services.py
|
|
@ -20,7 +20,7 @@ from .crud import (
|
|||
update_event,
|
||||
update_ticket,
|
||||
)
|
||||
from .models import Ticket
|
||||
from .models import Event, Ticket
|
||||
|
||||
DEFAULT_NOSTR_RELAYS = [
|
||||
"wss://relay.damus.io",
|
||||
|
|
@ -55,16 +55,7 @@ async def _send_ticket_notification(ticket: Ticket) -> None:
|
|||
logger.warning(f"Event {ticket.event} not found for ticket notification.")
|
||||
return
|
||||
|
||||
ticket_url = _ticket_url(ticket)
|
||||
subject = (
|
||||
event.extra.notification_subject.strip()
|
||||
or f"Your ticket for '{event.name}' is ready"
|
||||
)
|
||||
body = (
|
||||
event.extra.notification_body.strip()
|
||||
or f"Your ticket for '{event.name}' is ready."
|
||||
)
|
||||
message = f"{body}\n\nOpen it here: {ticket_url}"
|
||||
subject, message = _ticket_notification_message(ticket, event)
|
||||
updated = False
|
||||
|
||||
if (
|
||||
|
|
@ -97,6 +88,35 @@ async def _send_ticket_notification(ticket: Ticket) -> None:
|
|||
await update_ticket(ticket)
|
||||
|
||||
|
||||
async def resend_ticket_email_notification(ticket: Ticket) -> Ticket:
|
||||
event = await get_event(ticket.event)
|
||||
if not event:
|
||||
raise ValueError("Event does not exist.")
|
||||
if not settings.lnbits_email_notifications_enabled:
|
||||
raise ValueError("Email notifications are not enabled.")
|
||||
if not ticket.email:
|
||||
raise ValueError("Ticket does not have an email address.")
|
||||
|
||||
subject, message = _ticket_notification_message(ticket, event)
|
||||
await send_email_notification([ticket.email], message, subject)
|
||||
ticket.extra.email_notification_sent = True
|
||||
return await update_ticket(ticket)
|
||||
|
||||
|
||||
def _ticket_notification_message(ticket: Ticket, event: Event) -> tuple[str, str]:
|
||||
ticket_url = _ticket_url(ticket)
|
||||
subject = (
|
||||
event.extra.notification_subject.strip()
|
||||
or f"Your ticket for '{event.name}' is ready"
|
||||
)
|
||||
body = (
|
||||
event.extra.notification_body.strip()
|
||||
or f"Your ticket for '{event.name}' is ready."
|
||||
)
|
||||
|
||||
return subject, f"{body}\n\nOpen it here: {ticket_url}"
|
||||
|
||||
|
||||
async def _send_nostr_ticket_notification(identifier: str, message: str) -> None:
|
||||
if "@" in identifier:
|
||||
await send_user_notification(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ window.PageEvents = {
|
|||
return {
|
||||
events: [],
|
||||
tickets: [],
|
||||
resendingTicketEmails: [],
|
||||
currencies: [],
|
||||
eventsTable: {
|
||||
columns: [
|
||||
|
|
@ -145,6 +146,35 @@ window.PageEvents = {
|
|||
.catch(LNbits.utils.notifyApiError)
|
||||
})
|
||||
},
|
||||
resendTicketEmail(ticket) {
|
||||
if (!ticket.paid || !ticket.email) return
|
||||
const wallet = _.findWhere(this.g.user.wallets, {id: ticket.wallet})
|
||||
if (!wallet) return
|
||||
|
||||
this.resendingTicketEmails.push(ticket.id)
|
||||
LNbits.api
|
||||
.request(
|
||||
'POST',
|
||||
'/events/api/v1/tickets/' + ticket.id + '/resend-email',
|
||||
wallet.adminkey
|
||||
)
|
||||
.then(response => {
|
||||
this.tickets = this.tickets.map(obj =>
|
||||
obj.id === ticket.id ? response.data : obj
|
||||
)
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Ticket email resent.',
|
||||
icon: null
|
||||
})
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
.finally(() => {
|
||||
this.resendingTicketEmails = this.resendingTicketEmails.filter(
|
||||
ticketId => ticketId !== ticket.id
|
||||
)
|
||||
})
|
||||
},
|
||||
exportticketsCSV() {
|
||||
LNbits.utils.exportCSV(this.ticketsTable.columns, this.tickets)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -171,10 +171,12 @@
|
|||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
<q-th auto-width></q-th>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span v-text="col.label"></span>
|
||||
</q-th>
|
||||
<q-th auto-width></q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
|
|
@ -191,6 +193,20 @@
|
|||
target="_blank"
|
||||
></q-btn>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="resendTicketEmail(props.row)"
|
||||
icon="email"
|
||||
color="primary"
|
||||
:disable="!props.row.paid || !props.row.email"
|
||||
:loading="resendingTicketEmails.includes(props.row.id)"
|
||||
>
|
||||
<q-tooltip>Resend ticket email</q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span v-text="col.value"></span>
|
||||
|
|
|
|||
34
views_api.py
34
views_api.py
|
|
@ -52,7 +52,7 @@ from .models import (
|
|||
Ticket,
|
||||
TicketPaymentRequest,
|
||||
)
|
||||
from .services import refund_tickets
|
||||
from .services import refund_tickets, resend_ticket_email_notification
|
||||
from .tasks import deregister_payment_listener, register_payment_listener
|
||||
|
||||
events_api_router = APIRouter(prefix="/api/v1/events")
|
||||
|
|
@ -388,6 +388,38 @@ async def api_ticket_delete(
|
|||
await delete_ticket(ticket_id)
|
||||
|
||||
|
||||
@tickets_api_router.post("/{ticket_id}/resend-email")
|
||||
async def api_ticket_resend_email(
|
||||
ticket_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
) -> Ticket:
|
||||
ticket = await get_ticket(ticket_id)
|
||||
if not ticket:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Ticket does not exist."
|
||||
)
|
||||
|
||||
if ticket.wallet != wallet.wallet.id:
|
||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your ticket.")
|
||||
|
||||
if not ticket.paid:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
detail="Only paid tickets can be resent by email.",
|
||||
)
|
||||
|
||||
try:
|
||||
return await resend_ticket_email_notification(ticket)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to resend ticket email.",
|
||||
) from exc
|
||||
|
||||
|
||||
@tickets_api_router.put("/register/{ticket_id}")
|
||||
async def api_event_register_ticket(ticket_id) -> Ticket:
|
||||
ticket = await get_ticket(ticket_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue