-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bcde392
Showing
16 changed files
with
1,714 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Events | ||
|
||
## Sell tickets for events and use the built-in scanner for registering attendants | ||
|
||
Events alows you to make tickets for an event. Each ticket is in the form of a uniqque QR code. After registering, and paying for ticket, the user gets a QR code to present at registration/entrance. | ||
|
||
Events includes a shareable ticket scanner, which can be used to register attendees. | ||
|
||
## Usage | ||
|
||
1. Create an event\ | ||
![create event](https://i.imgur.com/dadK1dp.jpg) | ||
2. Fill out the event information: | ||
|
||
- event name | ||
- wallet (normally there's only one) | ||
- event information | ||
- closing date for event registration | ||
- begin and end date of the event | ||
|
||
![event info](https://imgur.com/KAv68Yr.jpg) | ||
|
||
3. Share the event registration link\ | ||
![event ticket](https://imgur.com/AQWUOBY.jpg) | ||
|
||
- ticket example\ | ||
![ticket example](https://i.imgur.com/trAVSLd.jpg) | ||
|
||
- QR code ticket, presented after invoice paid, to present at registration\ | ||
![event ticket](https://i.imgur.com/M0ROM82.jpg) | ||
|
||
4. Use the built-in ticket scanner to validate registered, and paid, attendees\ | ||
![ticket scanner](https://i.imgur.com/zrm9202.jpg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import asyncio | ||
|
||
from fastapi import APIRouter | ||
from fastapi.staticfiles import StaticFiles | ||
|
||
from lnbits.db import Database | ||
from lnbits.helpers import template_renderer | ||
from lnbits.tasks import catch_everything_and_restart | ||
|
||
db = Database("ext_events") | ||
|
||
|
||
events_ext: APIRouter = APIRouter(prefix="/events", tags=["Events"]) | ||
|
||
events_static_files = [ | ||
{ | ||
"path": "/events/static", | ||
"app": StaticFiles(packages=[("lnbits", "extensions/events/static")]), | ||
"name": "events_static", | ||
} | ||
] | ||
|
||
|
||
def events_renderer(): | ||
return template_renderer(["lnbits/extensions/events/templates"]) | ||
|
||
|
||
from .tasks import wait_for_paid_invoices | ||
from .views import * # noqa: F401,F403 | ||
from .views_api import * # noqa: F401,F403 | ||
|
||
|
||
def events_start(): | ||
loop = asyncio.get_event_loop() | ||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "Events", | ||
"short_description": "Sell and register event tickets", | ||
"tile": "/events/static/image/events.png", | ||
"contributors": ["benarc"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
from typing import List, Optional, Union | ||
|
||
from lnbits.helpers import urlsafe_short_hash | ||
|
||
from . import db | ||
from .models import CreateEvent, Events, Tickets | ||
|
||
# TICKETS | ||
|
||
|
||
async def create_ticket( | ||
payment_hash: str, wallet: str, event: str, name: str, email: str | ||
) -> Tickets: | ||
await db.execute( | ||
""" | ||
INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid) | ||
VALUES (?, ?, ?, ?, ?, ?, ?) | ||
""", | ||
(payment_hash, wallet, event, name, email, False, True), | ||
) | ||
|
||
# UPDATE EVENT DATA ON SOLD TICKET | ||
eventdata = await get_event(event) | ||
assert eventdata, "Couldn't get event from ticket being paid" | ||
sold = eventdata.sold + 1 | ||
amount_tickets = eventdata.amount_tickets - 1 | ||
await db.execute( | ||
""" | ||
UPDATE events.events | ||
SET sold = ?, amount_tickets = ? | ||
WHERE id = ? | ||
""", | ||
(sold, amount_tickets, event), | ||
) | ||
|
||
ticket = await get_ticket(payment_hash) | ||
assert ticket, "Newly created ticket couldn't be retrieved" | ||
return ticket | ||
|
||
|
||
async def get_ticket(payment_hash: str) -> Optional[Tickets]: | ||
row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,)) | ||
return Tickets(**row) if row else None | ||
|
||
|
||
async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]: | ||
if isinstance(wallet_ids, str): | ||
wallet_ids = [wallet_ids] | ||
|
||
q = ",".join(["?"] * len(wallet_ids)) | ||
rows = await db.fetchall( | ||
f"SELECT * FROM events.ticket WHERE wallet IN ({q})", (*wallet_ids,) | ||
) | ||
return [Tickets(**row) for row in rows] | ||
|
||
|
||
async def delete_ticket(payment_hash: str) -> None: | ||
await db.execute("DELETE FROM events.ticket WHERE id = ?", (payment_hash,)) | ||
|
||
|
||
async def delete_event_tickets(event_id: str) -> None: | ||
await db.execute("DELETE FROM events.ticket WHERE event = ?", (event_id,)) | ||
|
||
|
||
# EVENTS | ||
|
||
|
||
async def create_event(data: CreateEvent) -> Events: | ||
event_id = urlsafe_short_hash() | ||
await db.execute( | ||
""" | ||
INSERT INTO events.events (id, wallet, name, info, closing_date, event_start_date, event_end_date, amount_tickets, price_per_ticket, sold) | ||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
""", | ||
( | ||
event_id, | ||
data.wallet, | ||
data.name, | ||
data.info, | ||
data.closing_date, | ||
data.event_start_date, | ||
data.event_end_date, | ||
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 | ||
|
||
|
||
async def update_event(event_id: str, **kwargs) -> Events: | ||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) | ||
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 | ||
|
||
|
||
async def get_event(event_id: str) -> Optional[Events]: | ||
row = await db.fetchone("SELECT * FROM events.events WHERE id = ?", (event_id,)) | ||
return Events(**row) if row else None | ||
|
||
|
||
async def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]: | ||
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 [Events(**row) for row in rows] | ||
|
||
|
||
async def delete_event(event_id: str) -> None: | ||
await db.execute("DELETE FROM events.events WHERE id = ?", (event_id,)) | ||
|
||
|
||
# EVENTTICKETS | ||
|
||
|
||
async def get_event_tickets(event_id: str, wallet_id: str) -> List[Tickets]: | ||
rows = await db.fetchall( | ||
"SELECT * FROM events.ticket WHERE wallet = ? AND event = ?", | ||
(wallet_id, event_id), | ||
) | ||
return [Tickets(**row) for row in rows] | ||
|
||
|
||
async def reg_ticket(ticket_id: str) -> List[Tickets]: | ||
await db.execute( | ||
"UPDATE events.ticket SET registered = ? 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 [Tickets(**row) for row in rows] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
async def m001_initial(db): | ||
|
||
await db.execute( | ||
""" | ||
CREATE TABLE events.events ( | ||
id TEXT PRIMARY KEY, | ||
wallet TEXT NOT NULL, | ||
name TEXT NOT NULL, | ||
info TEXT NOT NULL, | ||
closing_date TEXT NOT NULL, | ||
event_start_date TEXT NOT NULL, | ||
event_end_date TEXT NOT NULL, | ||
amount_tickets INTEGER NOT NULL, | ||
price_per_ticket INTEGER NOT NULL, | ||
sold INTEGER NOT NULL, | ||
time TIMESTAMP NOT NULL DEFAULT """ | ||
+ db.timestamp_now | ||
+ """ | ||
); | ||
""" | ||
) | ||
|
||
await db.execute( | ||
""" | ||
CREATE TABLE events.tickets ( | ||
id TEXT PRIMARY KEY, | ||
wallet TEXT NOT NULL, | ||
event TEXT NOT NULL, | ||
name TEXT NOT NULL, | ||
email TEXT NOT NULL, | ||
registered BOOLEAN NOT NULL, | ||
time TIMESTAMP NOT NULL DEFAULT """ | ||
+ db.timestamp_now | ||
+ """ | ||
); | ||
""" | ||
) | ||
|
||
|
||
async def m002_changed(db): | ||
|
||
await db.execute( | ||
""" | ||
CREATE TABLE events.ticket ( | ||
id TEXT PRIMARY KEY, | ||
wallet TEXT NOT NULL, | ||
event TEXT NOT NULL, | ||
name TEXT NOT NULL, | ||
email TEXT NOT NULL, | ||
registered BOOLEAN NOT NULL, | ||
paid BOOLEAN NOT NULL, | ||
time TIMESTAMP NOT NULL DEFAULT """ | ||
+ db.timestamp_now | ||
+ """ | ||
); | ||
""" | ||
) | ||
|
||
for row in [list(row) for row in await db.fetchall("SELECT * FROM events.tickets")]: | ||
usescsv = "" | ||
|
||
for i in range(row[5]): | ||
if row[7]: | ||
usescsv += "," + str(i + 1) | ||
else: | ||
usescsv += "," + str(1) | ||
usescsv = usescsv[1:] | ||
await db.execute( | ||
""" | ||
INSERT INTO events.ticket ( | ||
id, | ||
wallet, | ||
event, | ||
name, | ||
email, | ||
registered, | ||
paid | ||
) | ||
VALUES (?, ?, ?, ?, ?, ?, ?) | ||
""", | ||
(row[0], row[1], row[2], row[3], row[4], row[5], True), | ||
) | ||
await db.execute("DROP TABLE events.tickets") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from fastapi.param_functions import Query | ||
from pydantic import BaseModel | ||
|
||
|
||
class CreateEvent(BaseModel): | ||
wallet: str | ||
name: str | ||
info: str | ||
closing_date: str | ||
event_start_date: str | ||
event_end_date: str | ||
amount_tickets: int = Query(..., ge=0) | ||
price_per_ticket: int = Query(..., ge=0) | ||
|
||
|
||
class CreateTicket(BaseModel): | ||
name: str | ||
email: str | ||
|
||
|
||
class Events(BaseModel): | ||
id: str | ||
wallet: str | ||
name: str | ||
info: str | ||
closing_date: str | ||
event_start_date: str | ||
event_end_date: str | ||
amount_tickets: int | ||
price_per_ticket: int | ||
sold: int | ||
time: int | ||
|
||
|
||
class Tickets(BaseModel): | ||
id: str | ||
wallet: str | ||
event: str | ||
name: str | ||
email: str | ||
registered: bool | ||
paid: bool | ||
time: int |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import asyncio | ||
|
||
from lnbits.core.models import Payment | ||
from lnbits.helpers import get_current_extension_name | ||
from lnbits.tasks import register_invoice_listener | ||
|
||
from .models import CreateTicket | ||
from .views_api import api_ticket_send_ticket | ||
|
||
|
||
async def wait_for_paid_invoices(): | ||
invoice_queue = asyncio.Queue() | ||
register_invoice_listener(invoice_queue, get_current_extension_name()) | ||
|
||
while True: | ||
payment = await invoice_queue.get() | ||
await on_invoice_paid(payment) | ||
|
||
|
||
async def on_invoice_paid(payment: Payment) -> None: | ||
# (avoid loops) | ||
if ( | ||
payment.extra | ||
and "events" == payment.extra.get("tag") | ||
and payment.extra.get("name") | ||
and payment.extra.get("email") | ||
): | ||
await api_ticket_send_ticket( | ||
payment.memo, | ||
payment.payment_hash, | ||
CreateTicket( | ||
name=str(payment.extra.get("name")), | ||
email=str(payment.extra.get("email")), | ||
), | ||
) | ||
return |
Oops, something went wrong.