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
3 changes: 2 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from .crud import db
from .tasks import wait_for_paid_invoices
from .views import events_generic_router
from .views_api import events_api_router
from .views_api import events_api_router, tickets_api_router

events_ext: APIRouter = APIRouter(prefix="/events", tags=["Events"])
events_ext.include_router(events_generic_router)
events_ext.include_router(events_api_router)
events_ext.include_router(tickets_api_router)

events_static_files = [
{
Expand Down
6 changes: 3 additions & 3 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"id": "events",
"version": "1.2.1",
"version": "1.3.0",
"name": "Events",
"repo": "https://github.com/lnbits/events",
"short_description": "Sell and register event tickets",
"description": "",
"tile": "/events/static/image/events.png",
"min_lnbits_version": "1.3.0",
"min_lnbits_version": "1.4.1",
"contributors": [
{
"name": "talvasconcelos",
"uri": "https://github.com/talvasconcelos",
"role": "Developer"
},
{
"name": "DNI",
"name": "dni",
"uri": "https://github.com/dni",
"role": "Developer"
},
Expand Down
25 changes: 25 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ class Event(BaseModel):
extra: EventExtra = Field(default_factory=EventExtra)


class PublicEvent(BaseModel):
id: str
name: str
info: str
closing_date: str
canceled: bool
event_start_date: str
event_end_date: str
banner: str | None


class TicketExtra(BaseModel):
applied_promo_code: str | None = None
sats_paid: int | None = None
Expand All @@ -83,3 +94,17 @@ class Ticket(BaseModel):
time: datetime
reg_timestamp: datetime
extra: TicketExtra = Field(default_factory=TicketExtra)


class PublicTicket(BaseModel):
event: str
name: str
registered: bool
paid: bool
time: datetime
reg_timestamp: datetime


class TicketPaymentRequest(BaseModel):
payment_hash: str
payment_request: str
185 changes: 113 additions & 72 deletions static/js/display.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
window.app = Vue.createApp({
el: '#vue',
mixins: [windowMixin],
window.PageEventsDisplay = {
template: '#page-events-display',
data() {
return {
eventErrorLabel: '',
event: null,
paymentReq: null,
redirectUrl: null,
formDialog: {
Expand All @@ -23,22 +24,33 @@ window.app = Vue.createApp({
show: false,
status: 'pending',
paymentReq: null
}
},
paymentDismissMsg: null,
paymentWebsocket: null
}
},
async created() {
this.info = event_info
this.info = this.info.substring(1, this.info.length - 1)
this.banner = event_banner
this.extra = event_extra
this.hasPromoCodes = has_promoCodes
this.eventId = this.$route.params.id
this.event = await this.getEvent()
},
computed: {
formatDescription() {
return LNbits.utils.convertMarkdown(this.info)
}
},
methods: {
async getEvent() {
try {
const {data} = await LNbits.api.request(
'GET',
`/events/api/v1/events/${this.eventId}`
)
return data
} catch (error) {
this.eventErrorLabel = 'Event unavailable.'
LNbits.utils.notifyApiError(error)
}
},
resetForm(e) {
e.preventDefault()
this.formDialog.data.name = ''
Expand All @@ -47,10 +59,14 @@ window.app = Vue.createApp({
},

closeReceiveDialog() {
const checker = this.receive.paymentChecker
dismissMsg()
clearInterval(paymentChecker)
setTimeout(() => {}, 10000)
if (this.paymentDismissMsg) {
this.paymentDismissMsg()
this.paymentDismissMsg = null
}
if (this.paymentWebsocket) {
this.paymentWebsocket.close()
this.paymentWebsocket = null
}
},
nameValidation(val) {
const regex = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/g
Expand All @@ -63,68 +79,93 @@ window.app = Vue.createApp({
const regex = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/
return regex.test(val) || 'Please enter valid email.'
},
Invoice() {
axios
.post(`/events/api/v1/tickets/${event_id}`, {
name: this.formDialog.data.name,
email: this.formDialog.data.email,
promo_code: this.formDialog.data.promo_code || null
})
.then(response => {
this.paymentReq = response.data.payment_request
this.paymentCheck = response.data.payment_hash
paymentSuccess(paymentHash) {
if (this.paymentDismissMsg) {
this.paymentDismissMsg()
this.paymentDismissMsg = null
}
this.paymentReq = null
this.formDialog.data.name = ''
this.formDialog.data.email = ''
Quasar.Notify.create({
type: 'positive',
message: 'Sent, thank you!',
icon: null
})
this.receive = {
show: false,
status: 'complete',
paymentReq: null
}
this.ticketLink = {
show: true,
data: {
link: `/events/ticket/${paymentHash}`
}
}
setTimeout(() => {
window.location.href = `/events/ticket/${paymentHash}`
}, 5000)
},
async createInvoice() {
try {
const {data} = await LNbits.api.request(
'POST',
`/events/api/v1/tickets/${this.eventId}`,
null,
{
name: this.formDialog.data.name,
email: this.formDialog.data.email,
promo_code: this.formDialog.data.promo_code || null,
refund_address: this.formDialog.data.refund || null
}
)
this.paymentReq = data.payment_request
this.paymentHash = data.payment_hash

dismissMsg = Quasar.Notify.create({
timeout: 0,
message: 'Waiting for payment...'
})
this.paymentDismissMsg = Quasar.Notify.create({
timeout: 0,
message: 'Waiting for payment...'
})
this.receive = {
show: true,
status: 'pending',
paymentReq: this.paymentReq
}
this.websocketListener(this.paymentHash)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
websocketListener(paymentHash) {
if (this.paymentWebsocket) {
this.paymentWebsocket.close()
}

this.receive = {
show: true,
status: 'pending',
paymentReq: this.paymentReq
}
paymentChecker = setInterval(() => {
axios
.post(`/events/api/v1/tickets/${event_id}/${this.paymentCheck}`, {
event: event_id,
event_name: event_name,
name: this.formDialog.data.name,
email: this.formDialog.data.email
})
.then(res => {
if (res.data.paid) {
clearInterval(paymentChecker)
dismissMsg()
this.formDialog.data.name = ''
this.formDialog.data.email = ''
const url = new URL(window.location)
url.protocol = url.protocol === 'https:' ? 'wss' : 'ws'
url.pathname = `/events/api/v1/tickets/ws/${paymentHash}`
url.search = ''
url.hash = ''

Quasar.Notify.create({
type: 'positive',
message: 'Sent, thank you!',
icon: null
})
this.receive = {
show: false,
status: 'complete',
paymentReq: null
}
const ws = new WebSocket(url)
this.paymentWebsocket = ws

this.ticketLink = {
show: true,
data: {
link: `/events/ticket/${res.data.ticket_id}`
}
}
setTimeout(() => {
window.location.href = `/events/ticket/${res.data.ticket_id}`
}, 5000)
}
})
.catch(LNbits.utils.notifyApiError)
}, 2000)
})
.catch(LNbits.utils.notifyApiError)
ws.onmessage = event => {
const data = JSON.parse(event.data)
if (data.paid) {
this.paymentSuccess(paymentHash)
ws.close()
}
}
ws.onerror = error => {
console.error('WebSocket error:', error)
}
ws.onclose = () => {
if (this.paymentWebsocket === ws) {
this.paymentWebsocket = null
}
}
}
}
})
}
Loading
Loading