commit bcde392f41a5fdb3af01a1d75d662fbdb3b349d9
Author: Arc <33088785+arcbtc@users.noreply.github.com>
Date: Sat Feb 11 08:06:45 2023 +0000
Add files via upload
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..11b62fe
--- /dev/null
+++ b/README.md
@@ -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\
+ 
+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
+
+ 
+
+3. Share the event registration link\
+ 
+
+ - ticket example\
+ 
+
+ - QR code ticket, presented after invoice paid, to present at registration\
+ 
+
+4. Use the built-in ticket scanner to validate registered, and paid, attendees\
+ 
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..b928336
--- /dev/null
+++ b/__init__.py
@@ -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))
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..a62bcc4
--- /dev/null
+++ b/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Events",
+ "short_description": "Sell and register event tickets",
+ "tile": "/events/static/image/events.png",
+ "contributors": ["benarc"]
+}
diff --git a/crud.py b/crud.py
new file mode 100644
index 0000000..12cc732
--- /dev/null
+++ b/crud.py
@@ -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]
diff --git a/migrations.py b/migrations.py
new file mode 100644
index 0000000..5b9d53b
--- /dev/null
+++ b/migrations.py
@@ -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")
diff --git a/models.py b/models.py
new file mode 100644
index 0000000..dd38e97
--- /dev/null
+++ b/models.py
@@ -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
diff --git a/static/image/events.png b/static/image/events.png
new file mode 100644
index 0000000..65c1bdd
Binary files /dev/null and b/static/image/events.png differ
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 0000000..945e2d2
--- /dev/null
+++ b/tasks.py
@@ -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
diff --git a/templates/events/_api_docs.html b/templates/events/_api_docs.html
new file mode 100644
index 0000000..9a24d70
--- /dev/null
+++ b/templates/events/_api_docs.html
@@ -0,0 +1,25 @@
+
+ Events alows you to make a wave of tickets for an event, each ticket is
+ in the form of a unqiue QRcode, which the user presents at registration.
+ Events comes with a shareable ticket scanner, which can be used to
+ register attendees.
+ Events: Sell and register ticket waves for an event
+
+
+
+ Created by,
+ Ben Arc
+
+
You'll be redirected in a few moments...
+