feat: update to lnbits 1.0.0 (#36)
This commit is contained in:
parent
9ca714d878
commit
6714dcddc7
17 changed files with 1769 additions and 1772 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Events",
|
"name": "Events",
|
||||||
"short_description": "Sell and register event tickets",
|
"short_description": "Sell and register event tickets",
|
||||||
"tile": "/events/static/image/events.png",
|
"tile": "/events/static/image/events.png",
|
||||||
"min_lnbits_version": "0.12.5",
|
"min_lnbits_version": "1.0.0",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "talvasconcelos",
|
"name": "talvasconcelos",
|
||||||
|
|
|
||||||
186
crud.py
186
crud.py
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import List, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
@ -12,177 +12,103 @@ db = Database("ext_events")
|
||||||
async def create_ticket(
|
async def create_ticket(
|
||||||
payment_hash: str, wallet: str, event: str, name: str, email: str
|
payment_hash: str, wallet: str, event: str, name: str, email: str
|
||||||
) -> Ticket:
|
) -> Ticket:
|
||||||
await db.execute(
|
now = datetime.now(timezone.utc)
|
||||||
"""
|
ticket = Ticket(
|
||||||
INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid)
|
id=payment_hash,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
wallet=wallet,
|
||||||
""",
|
event=event,
|
||||||
(payment_hash, wallet, event, name, email, False, False),
|
name=name,
|
||||||
|
email=email,
|
||||||
|
registered=False,
|
||||||
|
paid=False,
|
||||||
|
reg_timestamp=now,
|
||||||
|
time=now,
|
||||||
)
|
)
|
||||||
|
await db.insert("events.ticket", ticket)
|
||||||
ticket = await get_ticket(payment_hash)
|
|
||||||
assert ticket, "Newly created ticket couldn't be retrieved"
|
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
|
|
||||||
async def set_ticket_paid(payment_hash: str) -> Ticket:
|
async def update_ticket(ticket: Ticket) -> Ticket:
|
||||||
ticket = await get_ticket(payment_hash)
|
await db.update("events.ticket", ticket)
|
||||||
assert ticket, "Ticket couldn't be retrieved"
|
|
||||||
if ticket.paid:
|
|
||||||
return ticket
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE events.ticket
|
|
||||||
SET paid = ?
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(True, ticket.id),
|
|
||||||
)
|
|
||||||
|
|
||||||
await update_event_sold(ticket.event)
|
|
||||||
|
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
|
|
||||||
async def update_event_sold(event_id: str):
|
|
||||||
event = await get_event(event_id)
|
|
||||||
assert event, "Couldn't get event from ticket being paid"
|
|
||||||
sold = event.sold + 1
|
|
||||||
amount_tickets = event.amount_tickets - 1
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE events.events
|
|
||||||
SET sold = ?, amount_tickets = ?
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(sold, amount_tickets, event_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
async def get_ticket(payment_hash: str) -> Optional[Ticket]:
|
async def get_ticket(payment_hash: str) -> Optional[Ticket]:
|
||||||
row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,))
|
return await db.fetchone(
|
||||||
return Ticket(**row) if row else None
|
"SELECT * FROM events.ticket WHERE id = :id",
|
||||||
|
{"id": payment_hash},
|
||||||
|
Ticket,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Ticket]:
|
async def get_tickets(wallet_ids: Union[str, list[str]]) -> list[Ticket]:
|
||||||
if isinstance(wallet_ids, str):
|
if isinstance(wallet_ids, str):
|
||||||
wallet_ids = [wallet_ids]
|
wallet_ids = [wallet_ids]
|
||||||
|
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
return await db.fetchall(
|
||||||
rows = await db.fetchall(
|
f"SELECT * FROM events.ticket WHERE wallet IN ({q})",
|
||||||
f"SELECT * FROM events.ticket WHERE wallet IN ({q})", (*wallet_ids,)
|
model=Ticket,
|
||||||
)
|
)
|
||||||
return [Ticket(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_ticket(payment_hash: str) -> None:
|
async def delete_ticket(payment_hash: str) -> None:
|
||||||
await db.execute("DELETE FROM events.ticket WHERE id = ?", (payment_hash,))
|
await db.execute("DELETE FROM events.ticket WHERE id = :id", {"id": payment_hash})
|
||||||
|
|
||||||
|
|
||||||
async def delete_event_tickets(event_id: str) -> None:
|
async def delete_event_tickets(event_id: str) -> None:
|
||||||
await db.execute("DELETE FROM events.ticket WHERE event = ?", (event_id,))
|
await db.execute(
|
||||||
|
"DELETE FROM events.ticket WHERE event = :event", {"event": event_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def purge_unpaid_tickets(event_id: str) -> None:
|
async def purge_unpaid_tickets(event_id: str) -> None:
|
||||||
time_diff = datetime.now() - timedelta(hours=24)
|
time_diff = datetime.now() - timedelta(hours=24)
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
DELETE FROM events.ticket WHERE event = ? AND paid = false
|
DELETE FROM events.ticket WHERE event = :event AND paid = false
|
||||||
AND time < {db.timestamp_placeholder}
|
AND time < {db.timestamp_placeholder("time")}
|
||||||
""",
|
""",
|
||||||
(
|
{"time": time_diff.timestamp(), "event": event_id},
|
||||||
event_id,
|
|
||||||
time_diff.timestamp(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def create_event(data: CreateEvent) -> Event:
|
async def create_event(data: CreateEvent) -> Event:
|
||||||
event_id = urlsafe_short_hash()
|
event_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
event = Event(id=event_id, time=datetime.now(timezone.utc), **data.dict())
|
||||||
"""
|
await db.insert("events.events", event)
|
||||||
INSERT INTO events.events (
|
|
||||||
id, wallet, name, info, banner, closing_date, event_start_date,
|
|
||||||
event_end_date, currency, amount_tickets, price_per_ticket, sold
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
event_id,
|
|
||||||
data.wallet,
|
|
||||||
data.name,
|
|
||||||
data.info,
|
|
||||||
data.banner,
|
|
||||||
data.closing_date,
|
|
||||||
data.event_start_date,
|
|
||||||
data.event_end_date,
|
|
||||||
data.currency,
|
|
||||||
data.amount_tickets,
|
|
||||||
data.price_per_ticket,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
event = await get_event(event_id)
|
|
||||||
assert event, "Newly created event couldn't be retrieved"
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
async def update_event(event_id: str, **kwargs) -> Event:
|
async def update_event(event: Event) -> Event:
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
await db.update("events.events", event)
|
||||||
await db.execute(
|
|
||||||
f"UPDATE events.events SET {q} WHERE id = ?", (*kwargs.values(), event_id)
|
|
||||||
)
|
|
||||||
event = await get_event(event_id)
|
|
||||||
assert event, "Newly updated event couldn't be retrieved"
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
async def get_event(event_id: str) -> Optional[Event]:
|
async def get_event(event_id: str) -> Optional[Event]:
|
||||||
row = await db.fetchone("SELECT * FROM events.events WHERE id = ?", (event_id,))
|
return await db.fetchone(
|
||||||
return Event(**row) if row else None
|
"SELECT * FROM events.events WHERE id = :id",
|
||||||
|
{"id": event_id},
|
||||||
|
Event,
|
||||||
async def get_events(wallet_ids: Union[str, List[str]]) -> List[Event]:
|
|
||||||
if isinstance(wallet_ids, str):
|
|
||||||
wallet_ids = [wallet_ids]
|
|
||||||
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
f"SELECT * FROM events.events WHERE wallet IN ({q})", (*wallet_ids,)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return [Event(**row) for row in rows]
|
|
||||||
|
async def get_events(wallet_ids: Union[str, list[str]]) -> list[Event]:
|
||||||
|
if isinstance(wallet_ids, str):
|
||||||
|
wallet_ids = [wallet_ids]
|
||||||
|
q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
|
||||||
|
return await db.fetchall(
|
||||||
|
f"SELECT * FROM events.events WHERE wallet IN ({q})",
|
||||||
|
model=Event,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_event(event_id: str) -> None:
|
async def delete_event(event_id: str) -> None:
|
||||||
await db.execute("DELETE FROM events.events WHERE id = ?", (event_id,))
|
await db.execute("DELETE FROM events.events WHERE id = :id", {"id": event_id})
|
||||||
|
|
||||||
|
|
||||||
# EVENTTICKETS
|
async def get_event_tickets(event_id: str) -> list[Ticket]:
|
||||||
|
return await db.fetchall(
|
||||||
|
"SELECT * FROM events.ticket WHERE event = :event",
|
||||||
async def get_event_tickets(event_id: str, wallet_id: str) -> List[Ticket]:
|
{"event": event_id},
|
||||||
rows = await db.fetchall(
|
Ticket,
|
||||||
"SELECT * FROM events.ticket WHERE wallet = ? AND event = ?",
|
|
||||||
(wallet_id, event_id),
|
|
||||||
)
|
)
|
||||||
return [Ticket(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def reg_ticket(ticket_id: str) -> List[Ticket]:
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
UPDATE events.ticket SET registered = ?,
|
|
||||||
reg_timestamp = {db.timestamp_now} WHERE id = ?
|
|
||||||
""",
|
|
||||||
(True, ticket_id),
|
|
||||||
)
|
|
||||||
ticket = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (ticket_id,))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
"SELECT * FROM events.ticket WHERE event = ?", (ticket[1],)
|
|
||||||
)
|
|
||||||
return [Ticket(**row) for row in rows]
|
|
||||||
|
|
|
||||||
13
models.py
13
models.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
|
@ -8,13 +9,13 @@ class CreateEvent(BaseModel):
|
||||||
wallet: str
|
wallet: str
|
||||||
name: str
|
name: str
|
||||||
info: str
|
info: str
|
||||||
banner: Optional[str]
|
|
||||||
closing_date: str
|
closing_date: str
|
||||||
event_start_date: str
|
event_start_date: str
|
||||||
event_end_date: str
|
event_end_date: str
|
||||||
currency: str = "sat"
|
currency: str = "sat"
|
||||||
amount_tickets: int = Query(..., ge=0)
|
amount_tickets: int = Query(..., ge=0)
|
||||||
price_per_ticket: float = Query(..., ge=0)
|
price_per_ticket: float = Query(..., ge=0)
|
||||||
|
banner: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CreateTicket(BaseModel):
|
class CreateTicket(BaseModel):
|
||||||
|
|
@ -27,15 +28,15 @@ class Event(BaseModel):
|
||||||
wallet: str
|
wallet: str
|
||||||
name: str
|
name: str
|
||||||
info: str
|
info: str
|
||||||
banner: Optional[str]
|
|
||||||
closing_date: str
|
closing_date: str
|
||||||
event_start_date: str
|
event_start_date: str
|
||||||
event_end_date: str
|
event_end_date: str
|
||||||
currency: str
|
currency: str
|
||||||
amount_tickets: int
|
amount_tickets: int
|
||||||
price_per_ticket: float
|
price_per_ticket: float
|
||||||
sold: int
|
time: datetime
|
||||||
time: int
|
sold: int = 0
|
||||||
|
banner: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class Ticket(BaseModel):
|
class Ticket(BaseModel):
|
||||||
|
|
@ -45,6 +46,6 @@ class Ticket(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
registered: bool
|
registered: bool
|
||||||
reg_timestamp: Optional[int]
|
|
||||||
paid: bool
|
paid: bool
|
||||||
time: int
|
time: datetime
|
||||||
|
reg_timestamp: datetime
|
||||||
|
|
|
||||||
2186
poetry.lock
generated
2186
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ authors = ["Alan Bits <alan@lnbits.com>"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10 | ^3.9"
|
python = "^3.10 | ^3.9"
|
||||||
lnbits = "*"
|
lnbits = {version = "*", allow-prereleases = true}
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^24.3.0"
|
black = "^24.3.0"
|
||||||
|
|
|
||||||
18
services.py
Normal file
18
services.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from .crud import get_event, update_event, update_ticket
|
||||||
|
from .models import Ticket
|
||||||
|
|
||||||
|
|
||||||
|
async def set_ticket_paid(ticket: Ticket) -> Ticket:
|
||||||
|
if ticket.paid:
|
||||||
|
return ticket
|
||||||
|
|
||||||
|
ticket.paid = True
|
||||||
|
await update_ticket(ticket)
|
||||||
|
|
||||||
|
event = await get_event(ticket.event)
|
||||||
|
assert event, "Couldn't get event from ticket being paid"
|
||||||
|
event.sold += 1
|
||||||
|
event.amount_tickets -= 1
|
||||||
|
await update_event(event)
|
||||||
|
|
||||||
|
return ticket
|
||||||
134
static/js/display.js
Normal file
134
static/js/display.js
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
paymentReq: null,
|
||||||
|
redirectUrl: null,
|
||||||
|
formDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
name: '',
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticketLink: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
link: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receive: {
|
||||||
|
show: false,
|
||||||
|
status: 'pending',
|
||||||
|
paymentReq: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.info = event_info
|
||||||
|
this.info = this.info.substring(1, this.info.length - 1)
|
||||||
|
this.banner = event_banner
|
||||||
|
await this.purgeUnpaidTickets()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formatDescription() {
|
||||||
|
return LNbits.utils.convertMarkdown(this.info)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetForm(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.formDialog.data.name = ''
|
||||||
|
this.formDialog.data.email = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
closeReceiveDialog() {
|
||||||
|
const checker = this.receive.paymentChecker
|
||||||
|
dismissMsg()
|
||||||
|
clearInterval(paymentChecker)
|
||||||
|
setTimeout(() => {}, 10000)
|
||||||
|
},
|
||||||
|
nameValidation(val) {
|
||||||
|
const regex = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/g
|
||||||
|
return (
|
||||||
|
!regex.test(val) ||
|
||||||
|
'Please enter valid name. No special character allowed.'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
emailValidation(val) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
this.paymentReq = response.data.payment_request
|
||||||
|
this.paymentCheck = response.data.payment_hash
|
||||||
|
|
||||||
|
dismissMsg = Quasar.Notify.create({
|
||||||
|
timeout: 0,
|
||||||
|
message: 'Waiting for payment...'
|
||||||
|
})
|
||||||
|
|
||||||
|
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 = ''
|
||||||
|
|
||||||
|
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/${res.data.ticket_id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/events/ticket/${res.data.ticket_id}`
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
async purgeUnpaidTickets() {
|
||||||
|
try {
|
||||||
|
await LNbits.api.request('GET', `/events/api/v1/purge/${event_id}`)
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
228
static/js/index.js
Normal file
228
static/js/index.js
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
const mapEvents = function (obj) {
|
||||||
|
obj.date = Quasar.date.formatDate(
|
||||||
|
new Date(obj.time * 1000),
|
||||||
|
'YYYY-MM-DD HH:mm'
|
||||||
|
)
|
||||||
|
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.price_per_ticket)
|
||||||
|
obj.displayUrl = ['/events/', obj.id].join('')
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
events: [],
|
||||||
|
tickets: [],
|
||||||
|
currencies: [],
|
||||||
|
eventsTable: {
|
||||||
|
columns: [
|
||||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
|
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
||||||
|
{name: 'info', align: 'left', label: 'Info', field: 'info'},
|
||||||
|
{name: 'banner', align: 'left', label: 'Banner', field: 'banner'},
|
||||||
|
{
|
||||||
|
name: 'event_start_date',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Start date',
|
||||||
|
field: 'event_start_date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'event_end_date',
|
||||||
|
align: 'left',
|
||||||
|
label: 'End date',
|
||||||
|
field: 'event_end_date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'closing_date',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Ticket close',
|
||||||
|
field: 'closing_date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'price_per_ticket',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Price',
|
||||||
|
field: row => {
|
||||||
|
if (row.currency != 'sats') {
|
||||||
|
return LNbits.utils.formatCurrency(
|
||||||
|
row.price_per_ticket.toFixed(2),
|
||||||
|
row.currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return row.price_per_ticket
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount_tickets',
|
||||||
|
align: 'left',
|
||||||
|
label: 'No tickets',
|
||||||
|
field: 'amount_tickets'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sold',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Sold',
|
||||||
|
field: 'sold'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ticketsTable: {
|
||||||
|
columns: [
|
||||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
|
{name: 'event', align: 'left', label: 'Event', field: 'event'},
|
||||||
|
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
||||||
|
{name: 'email', align: 'left', label: 'Email', field: 'email'},
|
||||||
|
{
|
||||||
|
name: 'registered',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Registered',
|
||||||
|
field: 'registered'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTickets() {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/events/api/v1/tickets?all_wallets=true',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
this.tickets = response.data
|
||||||
|
.map(function (obj) {
|
||||||
|
return mapEvents(obj)
|
||||||
|
})
|
||||||
|
.filter(e => e.paid)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteTicket(ticketId) {
|
||||||
|
const tickets = _.findWhere(this.tickets, {id: ticketId})
|
||||||
|
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this ticket')
|
||||||
|
.onOk(() => {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/events/api/v1/tickets/' + ticketId,
|
||||||
|
_.findWhere(this.g.user.wallets, {id: tickets.wallet}).inkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
this.tickets = _.reject(this.tickets, function (obj) {
|
||||||
|
return obj.id == ticketId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exportticketsCSV() {
|
||||||
|
LNbits.utils.exportCSV(this.ticketsTable.columns, this.tickets)
|
||||||
|
},
|
||||||
|
getEvents() {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/events/api/v1/events?all_wallets=true',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
this.events = response.data.map(function (obj) {
|
||||||
|
return mapEvents(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendEventData() {
|
||||||
|
const wallet = _.findWhere(this.g.user.wallets, {
|
||||||
|
id: this.formDialog.data.wallet
|
||||||
|
})
|
||||||
|
const data = this.formDialog.data
|
||||||
|
|
||||||
|
if (data.id) {
|
||||||
|
this.updateEvent(wallet, data)
|
||||||
|
} else {
|
||||||
|
this.createEvent(wallet, data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createEvent(wallet, data) {
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', '/events/api/v1/events', wallet.adminkey, data)
|
||||||
|
.then(response => {
|
||||||
|
this.events.push(mapEvents(response.data))
|
||||||
|
this.formDialog.show = false
|
||||||
|
this.formDialog.data = {}
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
updateformDialog(formId) {
|
||||||
|
const link = _.findWhere(this.events, {id: formId})
|
||||||
|
this.formDialog.data = {...link}
|
||||||
|
this.formDialog.show = true
|
||||||
|
},
|
||||||
|
updateEvent(wallet, data) {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
'/events/api/v1/events/' + data.id,
|
||||||
|
wallet.adminkey,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
this.events = _.reject(this.events, function (obj) {
|
||||||
|
return obj.id == data.id
|
||||||
|
})
|
||||||
|
this.events.push(mapEvents(response.data))
|
||||||
|
this.formDialog.show = false
|
||||||
|
this.formDialog.data = {}
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
deleteEvent(eventsId) {
|
||||||
|
const events = _.findWhere(this.events, {id: eventsId})
|
||||||
|
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this form link?')
|
||||||
|
.onOk(() => {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
'/events/api/v1/events/' + eventsId,
|
||||||
|
_.findWhere(this.g.user.wallets, {id: events.wallet}).adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
this.events = _.reject(this.events, function (obj) {
|
||||||
|
return obj.id == eventsId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError(error))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exporteventsCSV() {
|
||||||
|
LNbits.utils.exportCSV(this.eventsTable.columns, this.events)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
if (this.g.user.wallets.length) {
|
||||||
|
this.getTickets()
|
||||||
|
this.getEvents()
|
||||||
|
this.currencies = await LNbits.api.getCurrencies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
78
static/js/register.js
Normal file
78
static/js/register.js
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
const mapEvents = function (obj) {
|
||||||
|
obj.date = Quasar.date.formatDate(
|
||||||
|
new Date(obj.time * 1000),
|
||||||
|
'YYYY-MM-DD HH:mm'
|
||||||
|
)
|
||||||
|
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
|
||||||
|
obj.displayUrl = ['/events/', obj.id].join('')
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tickets: [],
|
||||||
|
ticketsTable: {
|
||||||
|
columns: [
|
||||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
|
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
||||||
|
{
|
||||||
|
name: 'registered',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Registered',
|
||||||
|
field: 'registered'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendCamera: {
|
||||||
|
show: false,
|
||||||
|
camera: 'auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hoverEmail(tmp) {
|
||||||
|
this.tickets.data.emailtemp = tmp
|
||||||
|
},
|
||||||
|
closeCamera() {
|
||||||
|
this.sendCamera.show = false
|
||||||
|
},
|
||||||
|
showCamera() {
|
||||||
|
this.sendCamera.show = true
|
||||||
|
},
|
||||||
|
decodeQR(res) {
|
||||||
|
this.sendCamera.show = false
|
||||||
|
const value = res[0].rawValue.split('//')[1]
|
||||||
|
LNbits.api
|
||||||
|
.request('GET', `/events/api/v1/register/ticket/${value}`)
|
||||||
|
.then(() => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Registered!'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
getEventTickets() {
|
||||||
|
LNbits.api
|
||||||
|
.request('GET', `/events/api/v1/eventtickets/${event_id}`)
|
||||||
|
.then(response => {
|
||||||
|
this.tickets = response.data.map(obj => {
|
||||||
|
return mapEvents(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getEventTickets()
|
||||||
|
}
|
||||||
|
})
|
||||||
26
tasks.py
26
tasks.py
|
|
@ -2,8 +2,10 @@ import asyncio
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .crud import set_ticket_paid
|
from .crud import get_ticket
|
||||||
|
from .services import set_ticket_paid
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
|
|
@ -16,12 +18,16 @@ async def wait_for_paid_invoices():
|
||||||
|
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
async def on_invoice_paid(payment: Payment) -> None:
|
||||||
# (avoid loops)
|
if not payment.extra or "events" != payment.extra.get("tag"):
|
||||||
if (
|
return
|
||||||
payment.extra
|
|
||||||
and "events" == payment.extra.get("tag")
|
if not payment.extra.get("name") or not payment.extra.get("email"):
|
||||||
and payment.extra.get("name")
|
logger.warning(f"Ticket {payment.payment_hash} missing name or email.")
|
||||||
and payment.extra.get("email")
|
return
|
||||||
):
|
|
||||||
await set_ticket_paid(payment.payment_hash)
|
ticket = await get_ticket(payment.payment_hash)
|
||||||
return
|
if not ticket:
|
||||||
|
logger.warning(f"Ticket for payment {payment.payment_hash} not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await set_ticket_paid(ticket)
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,9 @@
|
||||||
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
<div class="text-center q-mb-lg">
|
<div class="text-center q-mb-lg">
|
||||||
<a class="text-secondary" :href="'lightning:' + receive.paymentReq">
|
<a class="text-secondary" :href="'lightning:' + receive.paymentReq">
|
||||||
<q-responsive :ratio="1" class="q-mx-xl">
|
<lnbits-qrcode
|
||||||
<qrcode
|
:value="'lightning:' + receive.paymentReq.toUpperCase()"
|
||||||
:value="'lightning:' + receive.paymentReq.toUpperCase()"
|
></lnbits-qrcode>
|
||||||
:options="{width: 340}"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
|
|
@ -94,152 +90,10 @@
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
const event_id = '{{ event_id }}'
|
||||||
|
const event_name = '{{ event_name }}'
|
||||||
new Vue({
|
const event_info = '{{ event_info | tojson }}'
|
||||||
el: '#vue',
|
const event_banner = JSON.parse('{{ event_banner | tojson | safe }}')
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
paymentReq: null,
|
|
||||||
redirectUrl: null,
|
|
||||||
formDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
name: '',
|
|
||||||
email: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ticketLink: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
link: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
receive: {
|
|
||||||
show: false,
|
|
||||||
status: 'pending',
|
|
||||||
paymentReq: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
this.info = '{{ event_info | tojson }}'
|
|
||||||
this.info = this.info.substring(1, this.info.length - 1)
|
|
||||||
this.banner = JSON.parse('{{ event_banner | tojson |safe }}')
|
|
||||||
await this.purgeUnpaidTickets()
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
formatDescription() {
|
|
||||||
return LNbits.utils.convertMarkdown(this.info)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resetForm: function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
this.formDialog.data.name = ''
|
|
||||||
this.formDialog.data.email = ''
|
|
||||||
},
|
|
||||||
|
|
||||||
closeReceiveDialog: function () {
|
|
||||||
var checker = this.receive.paymentChecker
|
|
||||||
dismissMsg()
|
|
||||||
|
|
||||||
clearInterval(paymentChecker)
|
|
||||||
setTimeout(function () {}, 10000)
|
|
||||||
},
|
|
||||||
nameValidation(val) {
|
|
||||||
const regex = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/g
|
|
||||||
return (
|
|
||||||
!regex.test(val) ||
|
|
||||||
'Please enter valid name. No special character allowed.'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
emailValidation(val) {
|
|
||||||
let regex = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/
|
|
||||||
return regex.test(val) || 'Please enter valid email.'
|
|
||||||
},
|
|
||||||
|
|
||||||
Invoice: function () {
|
|
||||||
var self = this
|
|
||||||
axios
|
|
||||||
.post(`/events/api/v1/tickets/{{ event_id }}`, {
|
|
||||||
name: self.formDialog.data.name,
|
|
||||||
email: self.formDialog.data.email
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
self.paymentReq = response.data.payment_request
|
|
||||||
self.paymentCheck = response.data.payment_hash
|
|
||||||
|
|
||||||
dismissMsg = self.$q.notify({
|
|
||||||
timeout: 0,
|
|
||||||
message: 'Waiting for payment...'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.receive = {
|
|
||||||
show: true,
|
|
||||||
status: 'pending',
|
|
||||||
paymentReq: self.paymentReq
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentChecker = setInterval(function () {
|
|
||||||
axios
|
|
||||||
.post(
|
|
||||||
`/events/api/v1/tickets/{{ event_id }}/${self.paymentCheck}`,
|
|
||||||
{
|
|
||||||
event: '{{ event_id }}',
|
|
||||||
event_name: '{{ event_name }}',
|
|
||||||
name: self.formDialog.data.name,
|
|
||||||
email: self.formDialog.data.email
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(function (res) {
|
|
||||||
if (res.data.paid) {
|
|
||||||
clearInterval(paymentChecker)
|
|
||||||
dismissMsg()
|
|
||||||
self.formDialog.data.name = ''
|
|
||||||
self.formDialog.data.email = ''
|
|
||||||
|
|
||||||
self.$q.notify({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Sent, thank you!',
|
|
||||||
icon: null
|
|
||||||
})
|
|
||||||
self.receive = {
|
|
||||||
show: false,
|
|
||||||
status: 'complete',
|
|
||||||
paymentReq: null
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ticketLink = {
|
|
||||||
show: true,
|
|
||||||
data: {
|
|
||||||
link: `/events/ticket/${res.data.ticket_id}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.href = `/events/ticket/${res.data.ticket_id}`
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
}, 2000)
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async purgeUnpaidTickets() {
|
|
||||||
try {
|
|
||||||
await LNbits.api.request('GET', `/events/api/v1/purge/{{ event_id }}`)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="{{ static_url_for('events/static', path='js/display.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,14 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,17 @@
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="events"
|
:rows="events"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="eventsTable.columns"
|
:columns="eventsTable.columns"
|
||||||
:pagination.sync="eventsTable.pagination"
|
v-model:pagination="eventsTable.pagination"
|
||||||
>
|
>
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="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">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.label }}
|
<span v-text="col.label"></span>
|
||||||
</q-th>
|
</q-th>
|
||||||
|
|
||||||
<q-th auto-width></q-th>
|
<q-th auto-width></q-th>
|
||||||
|
|
@ -67,7 +66,7 @@
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.value }}
|
<span v-text="col.value"></span>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -91,7 +90,6 @@
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
@ -111,17 +109,16 @@
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="tickets"
|
:rows="tickets"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="ticketsTable.columns"
|
:columns="ticketsTable.columns"
|
||||||
:pagination.sync="ticketsTable.pagination"
|
v-model:pagination="ticketsTable.pagination"
|
||||||
>
|
>
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="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">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.label }}
|
<span v-text="col.label"></span>
|
||||||
</q-th>
|
</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -141,7 +138,7 @@
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.value }}
|
<span v-text="col.value"></span>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
|
|
@ -156,7 +153,6 @@
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
@ -280,8 +276,8 @@
|
||||||
v-model.number="formDialog.data.price_per_ticket"
|
v-model.number="formDialog.data.price_per_ticket"
|
||||||
type="number"
|
type="number"
|
||||||
:label="'Price (' + formDialog.data.currency + ') *'"
|
:label="'Price (' + formDialog.data.currency + ') *'"
|
||||||
:step="formDialog.data.currency != 'sat' ? '0.01' : '1'"
|
:step="formDialog.data.currency != 'sats' ? '0.01' : '1'"
|
||||||
:mask="formDialog.data.currency != 'sat' ? '#.##' : '#'"
|
:mask="formDialog.data.currency != 'sats' ? '#.##' : '#'"
|
||||||
fill-mask="0"
|
fill-mask="0"
|
||||||
reverse-fill-mask
|
reverse-fill-mask
|
||||||
></q-input>
|
></q-input>
|
||||||
|
|
@ -318,264 +314,5 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script src="{{ static_url_for('events/static', path='js/index.js') }}"></script>
|
||||||
var mapEvents = function (obj) {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date(obj.time * 1000),
|
|
||||||
'YYYY-MM-DD HH:mm'
|
|
||||||
)
|
|
||||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
|
|
||||||
obj.displayUrl = ['/events/', obj.id].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
events: [],
|
|
||||||
tickets: [],
|
|
||||||
currencies: [],
|
|
||||||
eventsTable: {
|
|
||||||
columns: [
|
|
||||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
|
||||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
|
||||||
{name: 'info', align: 'left', label: 'Info', field: 'info'},
|
|
||||||
{name: 'banner', align: 'left', label: 'Banner', field: 'banner'},
|
|
||||||
{
|
|
||||||
name: 'event_start_date',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Start date',
|
|
||||||
field: 'event_start_date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'event_end_date',
|
|
||||||
align: 'left',
|
|
||||||
label: 'End date',
|
|
||||||
field: 'event_end_date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'closing_date',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Ticket close',
|
|
||||||
field: 'closing_date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'price_per_ticket',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Price',
|
|
||||||
field: row => {
|
|
||||||
if (row.currency != 'sat') {
|
|
||||||
return LNbits.utils.formatCurrency(
|
|
||||||
row.price_per_ticket.toFixed(2),
|
|
||||||
row.currency
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return row.price_per_ticket
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'amount_tickets',
|
|
||||||
align: 'left',
|
|
||||||
label: 'No tickets',
|
|
||||||
field: 'amount_tickets'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sold',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Sold',
|
|
||||||
field: 'sold'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ticketsTable: {
|
|
||||||
columns: [
|
|
||||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
|
||||||
{name: 'event', align: 'left', label: 'Event', field: 'event'},
|
|
||||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
|
||||||
{name: 'email', align: 'left', label: 'Email', field: 'email'},
|
|
||||||
{
|
|
||||||
name: 'registered',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Registered',
|
|
||||||
field: 'registered'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getTickets: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/events/api/v1/tickets?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.tickets = response.data
|
|
||||||
.map(function (obj) {
|
|
||||||
return mapEvents(obj)
|
|
||||||
})
|
|
||||||
.filter(e => e.paid)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteTicket: function (ticketId) {
|
|
||||||
var self = this
|
|
||||||
var tickets = _.findWhere(this.tickets, {id: ticketId})
|
|
||||||
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Are you sure you want to delete this ticket')
|
|
||||||
.onOk(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/events/api/v1/tickets/' + ticketId,
|
|
||||||
_.findWhere(self.g.user.wallets, {id: tickets.wallet}).inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.tickets = _.reject(self.tickets, function (obj) {
|
|
||||||
return obj.id == ticketId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
exportticketsCSV: function () {
|
|
||||||
LNbits.utils.exportCSV(this.ticketsTable.columns, this.tickets)
|
|
||||||
},
|
|
||||||
getEvents: function () {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/events/api/v1/events?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.events = response.data.map(function (obj) {
|
|
||||||
return mapEvents(obj)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
sendEventData: function () {
|
|
||||||
var wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: this.formDialog.data.wallet
|
|
||||||
})
|
|
||||||
var data = this.formDialog.data
|
|
||||||
|
|
||||||
if (data.id) {
|
|
||||||
this.updateEvent(wallet, data)
|
|
||||||
} else {
|
|
||||||
this.createEvent(wallet, data)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createEvent: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request('POST', '/events/api/v1/events', wallet.adminkey, data)
|
|
||||||
.then(function (response) {
|
|
||||||
self.events.push(mapEvents(response.data))
|
|
||||||
self.formDialog.show = false
|
|
||||||
self.formDialog.data = {}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateformDialog: function (formId) {
|
|
||||||
var link = _.findWhere(this.events, {id: formId})
|
|
||||||
|
|
||||||
this.formDialog.data = {...link}
|
|
||||||
|
|
||||||
this.formDialog.show = true
|
|
||||||
},
|
|
||||||
updateEvent: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
'/events/api/v1/events/' + data.id,
|
|
||||||
wallet.adminkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.events = _.reject(self.events, function (obj) {
|
|
||||||
return obj.id == data.id
|
|
||||||
})
|
|
||||||
self.events.push(mapEvents(response.data))
|
|
||||||
self.formDialog.show = false
|
|
||||||
self.formDialog.data = {}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteEvent: function (eventsId) {
|
|
||||||
var self = this
|
|
||||||
var events = _.findWhere(this.events, {id: eventsId})
|
|
||||||
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Are you sure you want to delete this form link?')
|
|
||||||
.onOk(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/events/api/v1/events/' + eventsId,
|
|
||||||
_.findWhere(self.g.user.wallets, {id: events.wallet}).adminkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.events = _.reject(self.events, function (obj) {
|
|
||||||
return obj.id == eventsId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
exporteventsCSV: function () {
|
|
||||||
LNbits.utils.exportCSV(this.eventsTable.columns, this.events)
|
|
||||||
},
|
|
||||||
async getCurrencies() {
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
'/events/api/v1/currencies',
|
|
||||||
this.inkey
|
|
||||||
)
|
|
||||||
|
|
||||||
this.currencies = ['sat', ...data]
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created: async function () {
|
|
||||||
if (this.g.user.wallets.length) {
|
|
||||||
this.getTickets()
|
|
||||||
this.getEvents()
|
|
||||||
await this.getCurrencies()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,16 @@
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="tickets"
|
:rows="tickets"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="ticketsTable.columns"
|
:columns="ticketsTable.columns"
|
||||||
:pagination.sync="ticketsTable.pagination"
|
v-model:pagination="ticketsTable.pagination"
|
||||||
>
|
>
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="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">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.label }}
|
<span v-text="col.label"></span>
|
||||||
</q-th>
|
</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -52,11 +51,10 @@
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.value }}
|
<span v-text="col.value"></span>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
</q-table>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
@ -66,7 +64,7 @@
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
<div class="text-center q-mb-lg">
|
<div class="text-center q-mb-lg">
|
||||||
<qrcode-stream
|
<qrcode-stream
|
||||||
@decode="decodeQR"
|
@detect="decodeQR"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode-stream>
|
></qrcode-stream>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,96 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
const event_id = '{{ event_id }}'
|
||||||
Vue.use(VueQrcodeReader)
|
|
||||||
var mapEvents = function (obj) {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date(obj.time * 1000),
|
|
||||||
'YYYY-MM-DD HH:mm'
|
|
||||||
)
|
|
||||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
|
|
||||||
obj.displayUrl = ['/events/', obj.id].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
tickets: [],
|
|
||||||
ticketsTable: {
|
|
||||||
columns: [
|
|
||||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
|
||||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
|
||||||
{
|
|
||||||
name: 'registered',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Registered',
|
|
||||||
field: 'registered'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sendCamera: {
|
|
||||||
show: false,
|
|
||||||
camera: 'auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hoverEmail: function (tmp) {
|
|
||||||
this.tickets.data.emailtemp = tmp
|
|
||||||
},
|
|
||||||
closeCamera: function () {
|
|
||||||
this.sendCamera.show = false
|
|
||||||
},
|
|
||||||
showCamera: function () {
|
|
||||||
this.sendCamera.show = true
|
|
||||||
},
|
|
||||||
decodeQR: function (res) {
|
|
||||||
this.sendCamera.show = false
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/events/api/v1/register/ticket/' + res.split('//')[1]
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.$q.notify({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Registered!'
|
|
||||||
})
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.reload()
|
|
||||||
}, 2000)
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getEventTickets: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/events/api/v1/eventtickets/{{ wallet_id }}/{{ event_id }}'
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.tickets = response.data.map(function (obj) {
|
|
||||||
return mapEvents(obj)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
this.getEventTickets()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="{{ static_url_for('events/static', path='js/register.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,10 @@
|
||||||
and present it for registration!
|
and present it for registration!
|
||||||
</h5>
|
</h5>
|
||||||
<br />
|
<br />
|
||||||
<q-responsive :ratio="1" class="q-mb-md" style="max-width: 300px">
|
<lnbits-qrcode
|
||||||
<qrcode
|
:value="'ticket://{{ ticket_id }}'"
|
||||||
:value="'ticket://{{ ticket_id }}'"
|
:options="{width: 500}"
|
||||||
:options="{width: 500}"
|
></lnbits-qrcode>
|
||||||
></qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
<br />
|
<br />
|
||||||
<q-btn @click="printWindow" color="grey" class="q-ml-auto">
|
<q-btn @click="printWindow" color="grey" class="q-ml-auto">
|
||||||
<q-icon left size="3em" name="print"></q-icon> Print</q-btn
|
<q-icon left size="3em" name="print"></q-icon> Print</q-btn
|
||||||
|
|
@ -28,15 +26,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
window.app = Vue.createApp({
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
printWindow: function () {
|
printWindow() {
|
||||||
window.print()
|
window.print()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
views.py
4
views.py
|
|
@ -2,7 +2,6 @@ from datetime import date, datetime
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
|
|
@ -12,7 +11,6 @@ from starlette.responses import HTMLResponse
|
||||||
from .crud import get_event, get_ticket
|
from .crud import get_event, get_ticket
|
||||||
|
|
||||||
events_generic_router = APIRouter()
|
events_generic_router = APIRouter()
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
|
|
||||||
def events_renderer():
|
def events_renderer():
|
||||||
|
|
@ -22,7 +20,7 @@ def events_renderer():
|
||||||
@events_generic_router.get("/", response_class=HTMLResponse)
|
@events_generic_router.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
return events_renderer().TemplateResponse(
|
return events_renderer().TemplateResponse(
|
||||||
"events/index.html", {"request": request, "user": user.dict()}
|
"events/index.html", {"request": request, "user": user.json()}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
70
views_api.py
70
views_api.py
|
|
@ -1,15 +1,16 @@
|
||||||
|
from datetime import datetime, timezone
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from lnbits.core.crud import get_standalone_payment, get_user
|
from lnbits.core.crud import get_standalone_payment, get_user
|
||||||
from lnbits.core.models import WalletTypeInfo
|
from lnbits.core.models import WalletTypeInfo
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
get_key_type,
|
|
||||||
require_admin_key,
|
require_admin_key,
|
||||||
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.utils.exchange_rates import (
|
from lnbits.utils.exchange_rates import (
|
||||||
currencies,
|
|
||||||
fiat_amount_as_satoshis,
|
fiat_amount_as_satoshis,
|
||||||
get_fiat_rate_satoshis,
|
get_fiat_rate_satoshis,
|
||||||
)
|
)
|
||||||
|
|
@ -27,18 +28,19 @@ from .crud import (
|
||||||
get_ticket,
|
get_ticket,
|
||||||
get_tickets,
|
get_tickets,
|
||||||
purge_unpaid_tickets,
|
purge_unpaid_tickets,
|
||||||
reg_ticket,
|
|
||||||
set_ticket_paid,
|
|
||||||
update_event,
|
update_event,
|
||||||
|
update_ticket,
|
||||||
)
|
)
|
||||||
from .models import CreateEvent, CreateTicket
|
from .models import CreateEvent, CreateTicket, Ticket
|
||||||
|
from .services import set_ticket_paid
|
||||||
|
|
||||||
events_api_router = APIRouter()
|
events_api_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@events_api_router.get("/api/v1/events")
|
@events_api_router.get("/api/v1/events")
|
||||||
async def api_events(
|
async def api_events(
|
||||||
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
|
all_wallets: bool = Query(False),
|
||||||
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
):
|
):
|
||||||
wallet_ids = [wallet.wallet.id]
|
wallet_ids = [wallet.wallet.id]
|
||||||
|
|
||||||
|
|
@ -53,8 +55,8 @@ async def api_events(
|
||||||
@events_api_router.put("/api/v1/events/{event_id}")
|
@events_api_router.put("/api/v1/events/{event_id}")
|
||||||
async def api_event_create(
|
async def api_event_create(
|
||||||
data: CreateEvent,
|
data: CreateEvent,
|
||||||
event_id=None,
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
|
event_id: Optional[str] = None,
|
||||||
):
|
):
|
||||||
if event_id:
|
if event_id:
|
||||||
event = await get_event(event_id)
|
event = await get_event(event_id)
|
||||||
|
|
@ -67,16 +69,18 @@ async def api_event_create(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your event."
|
status_code=HTTPStatus.FORBIDDEN, detail="Not your event."
|
||||||
)
|
)
|
||||||
event = await update_event(event_id, **data.dict())
|
for k, v in data.dict().items():
|
||||||
|
setattr(event, k, v)
|
||||||
|
event = await update_event(event)
|
||||||
else:
|
else:
|
||||||
event = await create_event(data=data)
|
event = await create_event(data)
|
||||||
|
|
||||||
return event.dict()
|
return event.dict()
|
||||||
|
|
||||||
|
|
||||||
@events_api_router.delete("/api/v1/events/{event_id}")
|
@events_api_router.delete("/api/v1/events/{event_id}")
|
||||||
async def api_form_delete(
|
async def api_form_delete(
|
||||||
event_id, wallet: WalletTypeInfo = Depends(require_admin_key)
|
event_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
event = await get_event(event_id)
|
event = await get_event(event_id)
|
||||||
if not event:
|
if not event:
|
||||||
|
|
@ -97,15 +101,16 @@ async def api_form_delete(
|
||||||
|
|
||||||
@events_api_router.get("/api/v1/tickets")
|
@events_api_router.get("/api/v1/tickets")
|
||||||
async def api_tickets(
|
async def api_tickets(
|
||||||
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
|
all_wallets: bool = Query(False),
|
||||||
):
|
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
||||||
|
) -> list[Ticket]:
|
||||||
wallet_ids = [wallet.wallet.id]
|
wallet_ids = [wallet.wallet.id]
|
||||||
|
|
||||||
if all_wallets:
|
if all_wallets:
|
||||||
user = await get_user(wallet.wallet.user)
|
user = await get_user(wallet.wallet.user)
|
||||||
wallet_ids = user.wallet_ids if user else []
|
wallet_ids = user.wallet_ids if user else []
|
||||||
|
|
||||||
return [ticket.dict() for ticket in await get_tickets(wallet_ids)]
|
return await get_tickets(wallet_ids)
|
||||||
|
|
||||||
|
|
||||||
@events_api_router.post("/api/v1/tickets/{event_id}")
|
@events_api_router.post("/api/v1/tickets/{event_id}")
|
||||||
|
|
@ -126,7 +131,7 @@ async def api_ticket_make_ticket(event_id, name, email):
|
||||||
price = event.price_per_ticket
|
price = event.price_per_ticket
|
||||||
extra = {"tag": "events", "name": name, "email": email}
|
extra = {"tag": "events", "name": name, "email": email}
|
||||||
|
|
||||||
if event.currency != "sat":
|
if event.currency != "sats":
|
||||||
price = await fiat_amount_as_satoshis(event.price_per_ticket, event.currency)
|
price = await fiat_amount_as_satoshis(event.price_per_ticket, event.currency)
|
||||||
|
|
||||||
extra["fiat"] = True
|
extra["fiat"] = True
|
||||||
|
|
@ -174,7 +179,7 @@ async def api_ticket_send_ticket(event_id, payment_hash):
|
||||||
assert payment
|
assert payment
|
||||||
price = (
|
price = (
|
||||||
event.price_per_ticket * 1000
|
event.price_per_ticket * 1000
|
||||||
if event.currency == "sat"
|
if event.currency == "sats"
|
||||||
else await fiat_amount_as_satoshis(event.price_per_ticket, event.currency)
|
else await fiat_amount_as_satoshis(event.price_per_ticket, event.currency)
|
||||||
* 1000
|
* 1000
|
||||||
)
|
)
|
||||||
|
|
@ -182,14 +187,16 @@ async def api_ticket_send_ticket(event_id, payment_hash):
|
||||||
lower_bound = price * 0.99 # 1% decrease
|
lower_bound = price * 0.99 # 1% decrease
|
||||||
|
|
||||||
if not payment.pending and abs(payment.amount) >= lower_bound: # allow 1% error
|
if not payment.pending and abs(payment.amount) >= lower_bound: # allow 1% error
|
||||||
await set_ticket_paid(payment_hash)
|
await set_ticket_paid(ticket)
|
||||||
return {"paid": True, "ticket_id": ticket.id}
|
return {"paid": True, "ticket_id": ticket.id}
|
||||||
|
|
||||||
return {"paid": False}
|
return {"paid": False}
|
||||||
|
|
||||||
|
|
||||||
@events_api_router.delete("/api/v1/tickets/{ticket_id}")
|
@events_api_router.delete("/api/v1/tickets/{ticket_id}")
|
||||||
async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
async def api_ticket_delete(
|
||||||
|
ticket_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
|
||||||
|
):
|
||||||
ticket = await get_ticket(ticket_id)
|
ticket = await get_ticket(ticket_id)
|
||||||
if not ticket:
|
if not ticket:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -200,11 +207,11 @@ async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your ticket.")
|
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your ticket.")
|
||||||
|
|
||||||
await delete_ticket(ticket_id)
|
await delete_ticket(ticket_id)
|
||||||
return "", HTTPStatus.NO_CONTENT
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: DELETE, updates db! @tal
|
||||||
@events_api_router.get("/api/v1/purge/{event_id}")
|
@events_api_router.get("/api/v1/purge/{event_id}")
|
||||||
async def api_event_purge_tickets(event_id):
|
async def api_event_purge_tickets(event_id: str):
|
||||||
event = await get_event(event_id)
|
event = await get_event(event_id)
|
||||||
if not event:
|
if not event:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -213,19 +220,14 @@ async def api_event_purge_tickets(event_id):
|
||||||
return await purge_unpaid_tickets(event_id)
|
return await purge_unpaid_tickets(event_id)
|
||||||
|
|
||||||
|
|
||||||
# Event Tickets
|
@events_api_router.get("/api/v1/eventtickets/{event_id}")
|
||||||
|
async def api_event_tickets(event_id: str) -> list[Ticket]:
|
||||||
|
return await get_event_tickets(event_id)
|
||||||
@events_api_router.get("/api/v1/eventtickets/{wallet_id}/{event_id}")
|
|
||||||
async def api_event_tickets(wallet_id, event_id):
|
|
||||||
return [
|
|
||||||
ticket.dict()
|
|
||||||
for ticket in await get_event_tickets(wallet_id=wallet_id, event_id=event_id)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: PUT, updates db! @tal
|
||||||
@events_api_router.get("/api/v1/register/ticket/{ticket_id}")
|
@events_api_router.get("/api/v1/register/ticket/{ticket_id}")
|
||||||
async def api_event_register_ticket(ticket_id):
|
async def api_event_register_ticket(ticket_id) -> list[Ticket]:
|
||||||
ticket = await get_ticket(ticket_id)
|
ticket = await get_ticket(ticket_id)
|
||||||
|
|
||||||
if not ticket:
|
if not ticket:
|
||||||
|
|
@ -243,9 +245,7 @@ async def api_event_register_ticket(ticket_id):
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="Ticket already registered"
|
status_code=HTTPStatus.FORBIDDEN, detail="Ticket already registered"
|
||||||
)
|
)
|
||||||
|
|
||||||
return [ticket.dict() for ticket in await reg_ticket(ticket_id)]
|
ticket.registered = True
|
||||||
|
ticket.reg_timestamp = datetime.now(timezone.utc)
|
||||||
|
await update_ticket(ticket)
|
||||||
@events_api_router.get("/api/v1/currencies")
|
return await get_event_tickets(ticket.event)
|
||||||
async def api_list_currencies_available():
|
|
||||||
return list(currencies.keys())
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue