diff --git a/config.json b/config.json
index 11bd0b1..a945b43 100644
--- a/config.json
+++ b/config.json
@@ -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",
diff --git a/services.py b/services.py
index 479aaa1..159bbdc 100644
--- a/services.py
+++ b/services.py
@@ -20,7 +20,7 @@
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(
diff --git a/static/js/index.js b/static/js/index.js
index d53ed90..5424a32 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -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)
},
diff --git a/static/js/index.vue b/static/js/index.vue
index 0c21058..fa39a4e 100644
--- a/static/js/index.vue
+++ b/static/js/index.vue
@@ -171,10 +171,12 @@
>
+
+
@@ -191,6 +193,20 @@
target="_blank"
>
+
+
+ Resend ticket email
+
+
diff --git a/views_api.py b/views_api.py
index f071db8..abdc1ef 100644
--- a/views_api.py
+++ b/views_api.py
@@ -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")
@@ -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)