Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
42 changes: 31 additions & 11 deletions services.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
update_event,
update_ticket,
)
from .models import Ticket
from .models import Event, Ticket

DEFAULT_NOSTR_RELAYS = [
"wss://relay.damus.io",
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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(
Expand Down
30 changes: 30 additions & 0 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ window.PageEvents = {
return {
events: [],
tickets: [],
resendingTicketEmails: [],
currencies: [],
eventsTable: {
columns: [
Expand Down Expand Up @@ -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)
},
Expand Down
16 changes: 16 additions & 0 deletions static/js/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -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>
Expand Down
34 changes: 33 additions & 1 deletion views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
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")
Expand Down Expand Up @@ -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)
Expand Down
Loading