Replaced events extension with almost working vuejs one

This commit is contained in:
benarc 2020-05-09 22:26:58 +01:00
parent 41277e6931
commit ea3b858695
16 changed files with 1273 additions and 2406 deletions

View File

@ -1,8 +1,11 @@
<h1> LNEVENTS</h1>
<h2>Create/sell tickets for an event</h2>
<p>Events is an easy way to create/sell tickets for an event.
<h1>Example Extension</h1>
<h2>*tagline*</h2>
This is an example extension to help you organise and build you own.
It is advised to setup a specific wallet in lnbits for the event.</p>
Try to include an image
<img src="https://i.imgur.com/9i4xcQB.png">
<img src="https://i.imgur.com/qHi7ExL.png">
<h2>If your extension has API endpoints, include useful ones here</h2>
<code>curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"</code>

View File

@ -1,6 +1,6 @@
{
"name": "Events",
"short_description": "LN tickets for events.",
"icon": "local_activity",
"contributors": ["arcbtc"]
"name": "Events",
"short_description": "Sell/register event tickets",
"icon": "local_activity",
"contributors": ["benarc"]
}

View File

@ -1,6 +0,0 @@
{
"name": "Events",
"short_description": "LN tickets for events.",
"icon": "local_activity",
"contributors": ["arcbtc"]
}

View File

@ -0,0 +1,116 @@
from typing import List, Optional, Union
from lnbits.db import open_ext_db
from lnbits.helpers import urlsafe_short_hash
from .models import Tickets, Events
#######TICKETS########
def create_ticket(wallet: str, event: str, name: str, email: str) -> Tickets:
with open_ext_db("events") as db:
eventdata = get_event(event)
sold = eventdata.sold + 1
amount_tickets = eventdata.amount_tickets - 1
ticket_id = urlsafe_short_hash()
db.execute(
"""
INSERT INTO tickets (id, wallet, event, name, email, registered)
VALUES (?, ?, ?, ?, ?, ?)
""",
(ticket_id, wallet, event, name, email, False),
)
db.execute(
"""
UPDATE events
SET sold = ?, amount_tickets = ?
WHERE id = ?
""",
(sold, amount_tickets, event),
)
return get_ticket(ticket_id)
def get_ticket(ticket_id: str) -> Optional[Tickets]:
with open_ext_db("events") as db:
row = db.fetchone("SELECT * FROM tickets WHERE id = ?", (ticket_id,))
return Tickets(**row) if row else None
def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
with open_ext_db("events") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM tickets WHERE wallet IN ({q})", (*wallet_ids,))
print("scrum")
return [Tickets(**row) for row in rows]
def delete_ticket(ticket_id: str) -> None:
with open_ext_db("events") as db:
db.execute("DELETE FROM tickets WHERE id = ?", (ticket_id,))
########EVENTS#########
def create_event(*, wallet: str, name: str, info: str, closing_date: str, event_start_date: str, event_end_date: str, amount_tickets: int, price_per_ticket: int) -> Events:
with open_ext_db("events") as db:
event_id = urlsafe_short_hash()
db.execute(
"""
INSERT INTO events (id, wallet, name, info, closing_date, event_start_date, event_end_date, amount_tickets, price_per_ticket, sold)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(event_id, wallet, name, info, closing_date, event_start_date, event_end_date, amount_tickets, price_per_ticket, 0),
)
print(event_id)
return get_event(event_id)
def update_event(event_id: str, **kwargs) -> Events:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
with open_ext_db("events") as db:
db.execute(f"UPDATE events SET {q} WHERE id = ?", (*kwargs.values(), event_id))
row = db.fetchone("SELECT * FROM events WHERE id = ?", (event_id,))
return Events(**row) if row else None
def get_event(event_id: str) -> Optional[Events]:
with open_ext_db("events") as db:
row = db.fetchone("SELECT * FROM events WHERE id = ?", (event_id,))
return Events(**row) if row else None
def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
with open_ext_db("events") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM events WHERE wallet IN ({q})", (*wallet_ids,))
return [Events(**row) for row in rows]
def delete_event(event_id: str) -> None:
with open_ext_db("events") as db:
db.execute("DELETE FROM events WHERE id = ?", (event_id,))
########EVENTTICKETS#########
def get_event_tickets(event_id: str, wallet_id: str) -> Tickets:
with open_ext_db("events") as db:
rows = db.fetchall("SELECT * FROM tickets WHERE wallet = ? AND event = ?", (wallet_id, event_id))
print(rows)
return [Tickets(**row) for row in rows]

View File

@ -1,5 +1,40 @@
from lnbits.db import open_ext_db
def m001_initial(db):
db.execute(
"""
CREATE TABLE IF NOT EXISTS 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 (strftime('%s', 'now'))
);
"""
)
db.execute(
"""
CREATE TABLE IF NOT EXISTS 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 (strftime('%s', 'now'))
);
"""
)
def migrate():
print("pending")
with open_ext_db("events") as db:
m001_initial(db)

View File

@ -0,0 +1,24 @@
from typing import NamedTuple
class Events(NamedTuple):
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(NamedTuple):
id: str
wallet: str
event: str
name: str
email: str
registered: bool
time: int

View File

@ -1,26 +0,0 @@
CREATE TABLE IF NOT EXISTS events (
key INTEGER PRIMARY KEY AUTOINCREMENT,
usr TEXT,
wal TEXT,
walnme TEXT,
walinvkey INTEGER,
uni TEXT,
tit TEXT,
cldate TEXT,
notickets INTEGER,
sold INTEGER DEFAULT 0,
prtick INTEGER,
descr TEXT,
unireg TEXT
);
CREATE TABLE IF NOT EXISTS eventssold (
key INTEGER PRIMARY KEY AUTOINCREMENT,
uni TEXT,
email TEXT,
name TEXT,
hash TEXT,
paid INTEGER DEFAULT 0,
reg INTEGER DEFAULT 0
);

View File

@ -0,0 +1,27 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="Info"
:content-inset-level="0.5"
>
<q-card>
<q-card-section>
<h5 class="text-subtitle1 q-my-none">Events: Sell and register tickets for an event</h5>
<p>Events alows you to make a wave of tickets for an event. Once an attendee has paid for a ticket they get a unqiue code. Events comes with a shareable scanning frontend, so you can register the attendees<br/>
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p>
</q-card>
</q-card-section>
</q-card-section>
</q-expansion-item>
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
</q-expansion-item>

View File

@ -1,567 +1,202 @@
<!-- @format -->
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<h3 class="q-my-none">{{ event_name }}</h3>
<br />
<h5 class="q-my-none">{{ event_info }}</h5>
<br />
<q-form @submit="Invoice()" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialog.data.name"
type="name"
label="Your name "
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.email"
type="email"
label="Your email "
></q-input>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>LNBits Wallet</title>
<meta
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
name="viewport"
/>
<!-- Bootstrap 3.3.2 -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"
/>
<!-- FontAwesome 4.3.0 -->
<link
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Ionicons 2.0.0 -->
<link
href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Theme style -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/>
<!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/>
<!-- Morris chart -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/morris/morris.css') }}"
/>
<!-- jvectormap -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}"
/>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<style>
.small-box > .small-box-footer {
text-align: left;
padding-left: 10px;
}
#loadingMessage {
text-align: center;
padding: 40px;
background-color: #eee;
}
#canvas {
width: 100%;
}
#output {
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
}
#output div {
padding-bottom: 10px;
word-wrap: break-word;
}
#noQRFound {
text-align: center;
}
</style>
<!-- jQuery 2.1.3 -->
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 -->
<script
src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript"
></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
<script>
$.widget.bridge('uibutton', $.ui.button)
</script>
<!-- Bootstrap 3.3.2 JS -->
<script
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"
type="text/javascript"
></script>
<!-- Morris.js charts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript"
></script>
<!-- Sparkline -->
<script
src="{{ url_for('static', filename='plugins/sparkline/jquery.sparkline.min.js') }}"
type="text/javascript"
></script>
<!-- jvectormap -->
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"
type="text/javascript"
></script>
<!-- jQuery Knob Chart -->
<script
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 -->
<script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll -->
<script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
type="text/javascript"
></script>
<!-- FastClick -->
<script src="{{ url_for('static', filename='plugins/fastclick/fastclick.min.js') }}"></script>
<!-- AdminLTE App -->
<script
src="{{ url_for('static', filename='dist/js/app.min.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
<script
src="{{ url_for('static', filename='dist/js/pages/dashboard.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE for demo purposes -->
<script
src="{{ url_for('static', filename='dist/js/demo.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/datatables/jquery.dataTables.js') }}"
type="text/javascript"
></script>
<link
rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<script
src="{{ url_for('static', filename='plugins/jscam/JS.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jscam/qrcode.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/decoder.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background: #1f2234;
}
body {
color: #fff;
}
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #1f2234;
border-left-color: #8964a9;
}
.skin-blue .main-header .navbar {
background-color: #2e507d;
}
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
padding: 15px;
color: #fff;
border-top: 0px;
}
.skin-blue .main-header .navbar {
background-color: #1f2234;
}
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
position: relative;
border-radius: 3px;
background-color: #333646;
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
.box.box-primary {
border-top-color: #8964a9;
}
a {
color: #8964a9;
}
.box-header.with-border {
border-bottom: none;
}
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
}
// .modal.in .modal-dialog{
// color:#000;
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="{{ url_for('core.home') }}" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a
href="#"
class="sidebar-toggle"
data-toggle="offcanvas"
role="button"
>
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
<p>{% raw %}{{amountWords}}{% endraw %}</p>
<div class="row q-mt-lg">
<q-btn
unelevated
color="deep-purple"
:disable="formDialog.data.name == '' || formDialog.data.email == '' || paymentReq"
type="submit"
>Submit</q-btn
>
<q-btn @click="resetForm" flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</nav>
</header>
</q-form>
</q-card-section>
</q-card>
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;"></section>
<!-- /.sidebar -->
</aside>
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content">
<br /><br />
<center><h1 style="font-size: 500%;">{{ nme }}</h1></center>
<center>
<h2 style="width: 55%; word-wrap: break-word;">{{ descr }}</h2>
</center>
<div id="theform">
<br /><br /><br />
<center>
<form role="form">
<div class="form-group" style="width: 300px;">
<input
id="Nam"
type="text"
class="form-control"
placeholder="Name"
/>
<input
id="Ema"
type="text"
class="form-control"
placeholder="Email"
/>
</div>
<button
onclick="submitforticket()"
type="button"
class="btn btn-info"
>
Go to payment
</button>
<p style="color: red;" id="error"></p>
</form>
</center>
</div>
<center>
<br /><br />
<div id="qrcode" style="width: 340px;"></div>
<br /><br />
<div
style="width: 55%; word-wrap: break-word;"
id="qrcodetxt"
></div>
<br />
</center>
</section>
<!-- /.content -->
<q-card v-show="ticketLink.show" class="q-pa-lg">
<div class="text-center q-mb-lg">
<a :href="ticketLink.data.link" target="_blank">
<q-btn unelevated size="xl" color="deep-purple"
>Link to your ticket!</q-btn
></a
>
<br /><br />
<p>You'll be redirected in 5 seconds...</p>
</div>
<!-- /.content-wrapper -->
</div>
</body>
</q-card>
</div>
<script>
function postAjax(url, data, thekey, success) {
var params =
typeof data == 'string'
? data
: Object.keys(data)
.map(function (k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
})
.join('&')
var xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('POST', url)
xhr.onreadystatechange = function () {
if (xhr.readyState > 3 && xhr.status == 200) {
success(xhr.responseText)
<q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog">
<q-card
v-if="!receive.paymentReq"
class="q-pa-lg q-pt-xl lnbits__dialog-card"
>
</q-card>
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-lg">
<a :href="'lightning:' + receive.paymentReq">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="paymentReq"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(receive.paymentReq)"
>Copy invoice</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
console.log('{{ form_costpword }}')
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
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
}
}
xhr.setRequestHeader('X-Api-Key', thekey)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(params)
return xhr
}
},
function getAjax(url, thekey, success) {
var xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState > 3 && xhr.status == 200) {
success(xhr.responseText)
}
}
xhr.setRequestHeader('X-Api-Key', thekey)
xhr.setRequestHeader('Content-Type', 'application/json')
methods: {
resetForm: function (e) {
e.preventDefault()
this.formDialog.data.name = ''
this.formDialog.data.email = ''
},
xhr.send()
return xhr
}
closeReceiveDialog: function () {
var checker = this.receive.paymentChecker
dismissMsg()
function submitforticket() {
nam = document.getElementById('Nam').value
ema = document.getElementById('Ema').value
clearInterval(paymentChecker)
setTimeout(function () {}, 10000)
},
Invoice: function () {
var self = this
axios
postAjax(
"{{ url_for('events.api_getticket') }}?ema=" + ema,
JSON.stringify({unireg: '{{wave }}', name: nam}),
'filla',
.get('/events/api/v1/tickets/' + '{{ event_id }}/{{ event_price }}')
function (data) {
theinvoice = JSON.parse(data).pay_req
thehash = JSON.parse(data).payment_hash
.then(function (response) {
self.paymentReq = response.data.payment_request
self.paymentCheck = response.data.checking_id
new QRCode(document.getElementById('qrcode'), {
text: theinvoice,
width: 300,
height: 300,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
})
document.getElementById('theform').innerHTML = ''
document.getElementById('qrcode').style.backgroundColor = 'white'
document.getElementById('qrcode').style.padding = '20px'
document.getElementById('qrcodetxt').innerHTML =
theinvoice + '<br/><br/>'
var refreshId = setInterval(function () {
getAjax('/api/v1/invoice/' + thehash, '{{wave}}', function (datab) {
console.log(JSON.parse(datab).PAID)
if (JSON.parse(datab).PAID == 'TRUE') {
location.replace(
"{{ url_for('events.ticket') }}?hash=" +
thehash +
'&unireg={{wave}}'
)
clearInterval(refreshId)
}
dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
}, 3000)
}
)
self.receive = {
show: true,
status: 'pending',
paymentReq: self.paymentReq
}
paymentChecker = setInterval(function () {
axios
.post('/events/api/v1/tickets/' + self.paymentCheck, {
event: '{{ event_id }}',
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)
})
}
}
</script>
</html>
})
</script>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<center>
<h3 class="q-my-none">{{ event_name }} error</h3>
<br />
<q-icon
name="warning"
class="text-grey"
style="font-size: 20rem;"
></q-icon>
<h5 class="q-my-none">{{ event_error }}</h5>
<br />
</center>
</q-card-section>
</q-card>
</div>
{% endblock %} {% block scripts %}
<script>
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {}
}
})
</script>
{% endblock %}
</div>

View File

@ -1,501 +1,512 @@
<!-- @format -->
{% extends "legacy.html" %} {% block messages %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-danger">!</span>
</a>
<ul class="dropdown-menu">
<li class="header"><b>Instant wallet, bookmark to save</b></li>
<li></li>
</ul>
{% endblock %} {% block menuitems %}
<li class="treeview">
<a href="#">
<i class="fa fa-bitcoin"></i> <span>Wallets</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for w in user_wallets %}
<li>
<a href="{{ url_for('wallet') }}?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a
>
</li>
{% endfor %}
<li><a onclick="sidebarmake()">Add a wallet +</a></li>
<div id="sidebarmake"></div>
</ul>
</li>
<li class="active treeview">
<a href="#">
<i class="fa fa-th"></i> <span>Extensions</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for extension in EXTENSIONS %} {% if extension.code in user_ext %}
<li>
<a href="{{ url_for(extension.code + '.index') }}?usr={{ user }}"
><i class="fa fa-plus"></i> {{ extension.name }}</a
>
</li>
{% endif %} {% endfor %}
<li>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}">Manager</a>
</li>
</ul>
</li>
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Events
<small>bitcoin tickets</small>
</h1>
<ol class="breadcrumb">
<li>
<a href="{{ url_for('wallet') }}?usr={{ user }}"
><i class="fa fa-dashboard"></i> Home</a
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
>New Event</q-btn
>
</li>
<li>
<a href="{{ url_for('core.extensions') }}?usr={{ user }}"
><li class="fa fa-dashboard">Extensions</li></a
>
</li>
<li>
<i class="active" class="fa fa-dashboard">Lightning tickets</i>
</li>
</ol>
<br /><br />
</section>
<style>
.datepicker-days {
background-color: #1f2234;
}
</style>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-6">
<!-- general form elements -->
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">Make a ticket wave</h3>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Events</h5>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form">
<div class="box-body">
<div class="form-group">
<label for="exampleInputEmail1">Ticket title</label>
<input
id="tit"
type="text"
pattern="^[A-Za-z]+$"
class="form-control"
/>
</div>
<div class="form-group">
<label>Description of event</label>
<textarea id="descr" class="form-control" rows="2"></textarea>
</div>
<!-- select -->
<div class="form-group">
<label>Select a wallet</label>
<select id="wal" class="form-control">
<option></option>
{% for w in user_wallets %}
<option>{{w.name}}-{{w.id}}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="nooftickets">No. of tickets</label>
<input
id="notickets"
type="number"
class="form-control"
placeholder="10"
max="86400"
/>
</div>
</div>
<div class="form-group">
<label>Close date:</label>
<div class="form-group">
<input type="text" class="form-control" id="datepicker" />
</div>
<div class="form-group">
<label for="prpertick">Price per ticket</label>
<input
id="prtickets"
type="number"
class="form-control"
placeholder="10"
/>
</div>
</div>
<div class="box-footer">
<button onclick="postev()" type="button" class="btn btn-info">
Create Wave
</button>
<p style="color: red;" id="error"></p>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<!-- general form elements -->
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">Select a link</h3>
</div>
<!-- /.box-header -->
<form role="form">
<div class="box-body">
<div class="form-group">
<select
class="form-control"
id="waveselect"
onchange="drawwithdraw()"
>
<option value="none" selected>
Select an Option
</option>
{% for w in user_ev %}
<option id="{{w.uni}}" value="{{w.tit}}-{{w.unireg}}-{{w.uni}}"
>{{w.tit}}-{{w.unireg}}-{{w.uni}}</option
>
{% endfor %}
</select>
</div>
<center>
<br />
<div id="qrcode" style="width: 340px;"></div>
<br />
<div
style="width: 75%; word-wrap: break-word;"
id="qrcodetxt"
></div>
</center>
</div>
</form>
</div>
<!-- /.box -->
</div>
<div class="row">
<div class="col-md-6">
<div class="box">
<div class="box-header">
<h3 class="box-title">Ticket Waves<b id="withdraws"></b></h3>
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<table
id="pagnation"
class="table table-bswearing anchorordered table-striped"
<div class="col-auto">
<q-btn flat color="grey" @click="exporteventsCSV"
>Export to CSV</q-btn
>
<tr>
<th>Title</th>
<th style="width: 15%;">Amt</th>
<th style="width: 15%;">Sold</th>
<th style="width: 15%;">Closing</th>
<th style="width: 15%;">Price</th>
<th style="width: 15%;">Wallet</th>
<th style="width: 10%;">Edit</th>
<th style="width: 10%;">Del</th>
</tr>
<tbody id="ticketwaves"></tbody>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
<q-table
dense
flat
:data="events"
row-key="id"
:columns="eventsTable.columns"
:pagination.sync="eventsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<div id="editlink"></div>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<!-- /.content -->
</section>
<script>
//Date picker
$('#datepicker').datepicker({
autoclose: true
})
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="link"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.displayUrl"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="how_to_reg"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="'/events/register/' + props.row.id"
target="_blank"
></q-btn>
</q-td>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="updateformDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="deleteEvent(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
window.user = {{ user | tojson | safe }}
window.user_wallets = {{ user_wallets | tojson | safe }}
window.user_ext = {{ user_ext | tojson | safe }}
window.user_ev = {{ user_ev | tojson | safe }}
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Tickets</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportticketsCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="tickets"
row-key="id"
:columns="ticketsTable.columns"
:pagination.sync="ticketsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="local_activity"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="'/events/ticket/' + props.row.id"
target="_blank"
></q-btn>
</q-td>
const user_ev = window.user_ev
console.log(user_ev)
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="deleteTicket(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">LNbits Events extension</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "events/_api_docs.html" %}
</q-list>
</q-card-section>
</q-card>
</div>
function drawChart(user_ev) {
var transactionsHTML = ''
for (var i = 0; i < user_ev.length; i++) {
var ev = user_ev[i]
transactionsHTML =
"<tr><td style='width: 50%'>" +
ev.tit +
'</td><td>' +
ev.notickets +
'</td><td>' +
ev.sold +
'</td><td>' +
ev.cldate +
'</td><td>' +
ev.prtick +
'</td><td>' +
"<a href='{{ url_for('wallet') }}?usr="+ ev.usr +"'>" + ev.wal.substring(0, 4) + "...</a>" +
'</td><td>' +
"<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" +
'</td><td>' +
"<b><a style='color:red;' href='" + "{{ url_for('events.index') }}?del=" + ev.uni + "&usr=" + ev.usr +"'>" + "<i class='fa fa-trash'></i>" + "</a></b>" +
'</td></tr>' +
transactionsHTML
document.getElementById('ticketwaves').innerHTML = transactionsHTML
<q-dialog v-model="formDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendEventData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input
filled
dense
v-model.trim="formDialog.data.name"
type="name"
label="Name of event "
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.info"
type="textarea"
label="Info about the event "
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.closing_date"
type="date"
label="Closing for tickets "
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.event_start_date"
type="date"
label="Event begins "
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.event_end_date"
type="date"
label="Event ends "
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.amount_tickets"
type="number"
label="Amount of tickets "
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.price_per_ticket"
type="number"
label="Price per ticket "
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
unelevated
color="deep-purple"
type="submit"
>Update Event</q-btn
>
<q-btn
v-else
unelevated
color="deep-purple"
:disable="formDialog.data.wallet == null || formDialog.data.name == null || formDialog.data.info == null || formDialog.data.closing_date == null || formDialog.data.event_start_date == null || formDialog.data.event_end_date == null || formDialog.data.amount_tickets == null || formDialog.data.price_per_ticket == null"
type="submit"
>Create Event</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<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: [],
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: '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: '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: {}
}
}
if (user_ev.length) {
drawChart(user_ev)
}
function postev(){
wal = document.getElementById('wal').value
tit = document.getElementById('tit').value
cldate = document.getElementById('datepicker').value
notickets = document.getElementById('notickets').value
prtickets = document.getElementById('prtickets').value
descr = document.getElementById('descr').value
if (tit == "") {
document.getElementById("error").innerHTML = "Only use letters in title"
return amt
}
if (wal == "") {
document.getElementById("error").innerHTML = "No wallet selected"
return amt
}
if (cldate == "") {
document.getElementById("error").innerHTML = "No date selected"
return amt
}
if (isNaN(notickets) || notickets < 1) {
document.getElementById("error").innerHTML = "Must be more than 1"
return amt
}
if (isNaN(prtickets) || prtickets < 10) {
document.getElementById("error").innerHTML = "Must be higher 10"
return amt
}
postAjax(
"{{ url_for('events.create') }}",
JSON.stringify({"tit": tit, "usr": user, "wal": wal, "notickets": notickets,"cldate": cldate, "prtickets": prtickets, "descr": descr}),
"filla",
function(data) { location.replace("{{ url_for('events.index') }}?usr=" + user)
})
}
function editlink(evnum){
evdetails = user_ev[evnum]
console.log(evdetails.descr)
wallpick = ""
checkbox = ""
if (evdetails.uniq == 1){
checkbox = "checked"}
document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<div class='col-md-6'>"+
" <!-- general form elements -->"+
"<div class='box box-primary' style='min-height: 300px;'>"+
"<div class='box-header'>"+
"<h3 class='box-title'> Edit: <i id='unid'>" + evdetails.tit + "-" + evdetails.uni + "-" + evdetails.unireg + "</i> </h3>"+
"<div class='box-tools pull-right'>" +
"<button class='btn btn-box-tool' data-widget='remove'><i class='fa fa-times'></i></button>" +
"</div>" +
" </div><!-- /.box-header -->"+
" <!-- form start -->"+
"<form role='form'>"+
"<div class='box-body'>"+
"<div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+
"<label for='exampleInputEmail1'>Link title</label>"+
"<input id='edittit' type='text' class='form-control' value='"+
evdetails.tit +
"'> </div>"+
" </div>"+
"<div class='col-sm-1 col-md-8'>"+
"<div class='form-group'>"+
"<label for='exampleInputEmail1'>Description of event</label>"+
"<textarea id='editdescr' type='textarea' rows='1' class='form-control'>"+evdetails.descr+"</textarea> </div>"+
" </div>"+
" <div class='col-sm-4 col-md-4'>"+
" <!-- select -->"+
" <div class='form-group'>"+
" <label>Select a wallet</label>"+
"<select id='editwal' class='form-control'>"+
" <option>" + evdetails.walnme + "-" + evdetails.wal + "</option>"+
" {% for w in user_wallets %}"+
" <option>{{w.name}}-{{w.id}}</option>"+
" {% endfor %}"+
" </select>"+
" </div>"+
" </div>"+
" <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+
" <label for='exampleInputPassword1'>No of tickets:</label>"+
" <input id='editnooftickets' type='number' class='form-control' placeholder='0' max='86400' value='"+
evdetails.notickets +
"'>"+
"</div> </div>"+
" <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+
"<label for='exampleInputEmail1'>Price per ticket:</label>"+
" <input id='editprtick' type='number' class='form-control' placeholder='1' value='"+
evdetails.prtick +
"'>"+
" </div></div>"+
" <div class='col-sm-3 col-md-4'>"+
" <div class='input-group date'>"+
" <label for='exampleInputEmail1'>Close date:</label>"+
" <input id='datepicker2' type='text' class='form-control' placeholder='1' value='"+
evdetails.cldate +
"'>"+
" </div></div>"+
" <div class='col-sm-3 col-md-4'>"+
"</div><!-- /.box-body -->"+
" </div><br/>"+
" <div class='box-footer'>"+
" <div class='col-sm-3 col-md-4'>"+
"<button onclick='editlinkcont()' type='button' style='margin: 24px;' class='btn btn-info'>Edit link(s)</button>"+
"</div>"+
"<p style='color:red;' id='error2'>.</p>"+
" </div></form></div><!-- /.box --></div></div>"
}
//Date picker
$('#datepicker2').datepicker({
autoclose: true
})
function editlinkcont(){
unid = document.getElementById('unid').innerHTML
wal = document.getElementById('editwal').value
tit = document.getElementById('edittit').value
nooftickets = document.getElementById('editnooftickets').value
prtick = document.getElementById('editprtick').value
cldate = document.getElementById('datepicker2').value
descr = document.getElementById('editdescr').value
uni = unid.split("-")[1]
if (tit == "") {
document.getElementById("error2").innerHTML = "Only use letters in title"
return amt
}
if (wal == "") {
document.getElementById("error2").innerHTML = "No wallet selected"
return amt
}
if (isNaN(nooftickets) || nooftickets < 1 || nooftickets > 1000000) {
document.getElementById("error2").innerHTML = "No. of tickets must be between 1 - 1000000"
return amt
}
if (isNaN(prtick) || prtick < 10 ) {
document.getElementById("error2").innerHTML = "Ticket pricket must be higher than 10"
return amt
}
postAjax(
"{{ url_for('events.create') }}",
JSON.stringify({"tit": tit, "usr": user, "wal": wal, "notickets": nooftickets,"cldate": cldate, "prtickets": prtick, "id": unid, "descr": descr}),
"filla",
function(data) { location.replace("{{ url_for('events.index') }}?usr=" + user)
})
}
//draws withdraw QR code
function drawwithdraw() {
document.getElementById("qrcode").innerHTML = "";
walname = document.getElementById("waveselect").value
thewave = walname.split("-");
console.log(window.location.hostname + "-" + thewave[1])
toencode = "/events/wave/" + thewave[1]
toreg = "/events/registration/" + thewave[2]
new QRCode(document.getElementById('qrcode'), {
text: toencode,
width: 300,
height: 300,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
},
methods: {
getTickets: function () {
var self = this
console.log('obj')
LNbits.api
.request(
'GET',
'/events/api/v1/tickets?all_wallets',
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.tickets = response.data.map(function (obj) {
console.log(obj)
return mapEvents(obj)
})
})
},
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)
},
document.getElementById('qrcodetxt').innerHTML = "<a href='" + toencode + "'><h2>Payment link</a> , " +
"<a href='" + toreg + "'>Registration</h2></a>"
getEvents: function () {
var self = this
document.getElementById("qrcode").style.backgroundColor = "white";
document.getElementById("qrcode").style.padding = "20px";
LNbits.api
.request(
'GET',
'/events/api/v1/events?all_wallets',
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.inkey, 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})
console.log(link.id)
this.formDialog.data.id = link.id
this.formDialog.data.wallet = link.wallet
this.formDialog.data.name = link.name
this.formDialog.data.info = link.info
this.formDialog.data.closing_date = link.closing_date
this.formDialog.data.event_start_date = link.event_start_date
this.formDialog.data.event_end_date = link.event_end_date
this.formDialog.data.amount_tickets = link.amount_tickets
this.formDialog.data.price_per_ticket = link.price_per_ticket
this.formDialog.show = true
},
updateEvent: function (wallet, data) {
var self = this
console.log(data)
LNbits.api
.request(
'PUT',
'/events/api/v1/events/' + data.id,
wallet.inkey,
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}).inkey
)
.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)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getTickets()
this.getEvents()
}
}
</script>
</div>
})
</script>
{% endblock %}

View File

@ -0,0 +1,146 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<center>
<h3 class="q-my-none">{{ event_name }} Registration</h3>
<br />
<br />
<q-btn unelevated color="deep-purple" @click="showCamera" size="xl"
>Scan ticket</q-btn
>
</center>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<q-table
dense
flat
:data="tickets"
row-key="id"
:columns="ticketsTable.columns"
:pagination.sync="ticketsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="local_activity"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="'/events/ticket/' + props.row.id"
target="_blank"
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block styles %}
<link
rel="stylesheet"
type="text/css"
href="{{ url_for('static', filename='vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.css') }}"
/>
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode)
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: true,
camera: 'auto'
}
}
},
methods: {
closeCamera: function () {
this.sendCamera.show = false
},
showCamera: function () {
this.sendCamera.show = true
},
getEventTickets: function () {
var self = this
console.log('obj')
LNbits.api
.request(
'GET',
'/events/api/v1/eventtickets/{{ wallet_id }}/{{ event_id }}'
)
.then(function (response) {
self.tickets = response.data.map(function (obj) {
console.log(obj)
return mapEvents(obj)
})
})
}
},
created: function () {
this.getEventTickets()
}
})
</script>
{% assets filters='rjsmin', output='__bundle__/core/chart.js',
'vendor/moment@2.25.1/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %} {% assets filters='rjsmin', output='__bundle__/core/wallet.js',
'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js',
'vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %} {% endblock %}

View File

@ -1,670 +0,0 @@
<!-- @format -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>LNBits Wallet</title>
<meta
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
name="viewport"
/>
<!-- Bootstrap 3.3.2 -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"
/>
<!-- FontAwesome 4.3.0 -->
<link
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Ionicons 2.0.0 -->
<link
href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Theme style -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/>
<!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/>
<!-- Morris chart -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/morris/morris.css') }}"
/>
<!-- jvectormap -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}"
/>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<style>
.small-box > .small-box-footer {
text-align: left;
padding-left: 10px;
}
#loadingMessage {
text-align: center;
padding: 40px;
background-color: #eee;
}
#canvas {
width: 100%;
}
#output {
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
}
#output div {
padding-bottom: 10px;
word-wrap: break-word;
}
#noQRFound {
text-align: center;
}
</style>
<!-- jQuery 2.1.3 -->
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 -->
<script
src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript"
></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
<script>
$.widget.bridge('uibutton', $.ui.button)
</script>
<!-- Bootstrap 3.3.2 JS -->
<script
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"
type="text/javascript"
></script>
<!-- Morris.js charts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript"
></script>
<!-- Sparkline -->
<script
src="{{ url_for('static', filename='plugins/sparkline/jquery.sparkline.min.js') }}"
type="text/javascript"
></script>
<!-- jvectormap -->
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"
type="text/javascript"
></script>
<!-- jQuery Knob Chart -->
<script
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 -->
<script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll -->
<script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
type="text/javascript"
></script>
<!-- FastClick -->
<script src="{{ url_for('static', filename='plugins/fastclick/fastclick.min.js') }}"></script>
<!-- AdminLTE App -->
<script
src="{{ url_for('static', filename='dist/js/app.min.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
<script
src="{{ url_for('static', filename='dist/js/pages/dashboard.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE for demo purposes -->
<script
src="{{ url_for('static', filename='dist/js/demo.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/datatables/jquery.dataTables.js') }}"
type="text/javascript"
></script>
<link
rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<script
src="{{ url_for('static', filename='plugins/jscam/JS.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jscam/qrcode.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/decoder.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background: #1f2234;
}
body {
color: #fff;
}
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #1f2234;
border-left-color: #8964a9;
}
.skin-blue .main-header .navbar {
background-color: #2e507d;
}
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
padding: 15px;
color: #fff;
border-top: 0px;
}
.skin-blue .main-header .navbar {
background-color: #1f2234;
}
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
position: relative;
border-radius: 3px;
background-color: #333646;
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
.box.box-primary {
border-top-color: #8964a9;
}
a {
color: #8964a9;
}
.box-header.with-border {
border-bottom: none;
}
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
}
// .modal.in .modal-dialog{
// color:#000;
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="{{ url_for('core.home') }}" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a
href="#"
class="sidebar-toggle"
data-toggle="offcanvas"
role="button"
>
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
</div>
</nav>
</header>
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;">
<!-- Sidebar user panel -->
</section>
<!-- /.sidebar -->
</aside>
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content">
<br /><br />
<center><h1 style="font-size: 500%;">{{ user_ev[0][6] }}</h1></center>
<br /><br /><br />
<div
class="modal fade sends"
tabindex="-1"
role="dialog"
aria-labelledby="myLargeModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div id="scantickets" style="padding: 0 10px 0 10px;"></div>
</div>
</div>
<center>
<button
onclick="scanQRsend()"
class="btn btn-block btn-primary btn-lg"
data-toggle="modal"
data-target=".sends"
style="width: 300px;"
>
Scan ticket
</button>
</center>
<div id="scantickets"></div>
<br /><br /><br />
<div id="qrcodetxt"></div>
<br />
<br /><br /><br />
<center>
<div class="row" style="width: 80%; margin-top: 80px;">
<style>
.ema,
button:focus .txt {
display: none;
}
button:focus .ema {
display: block;
}
</style>
<div class="box">
<div class="box-header">
<h3 class="box-title">Attendees<b id="withdraws"></b></h3>
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<table
id="pagnation"
class="table table-bswearing anchorordered table-striped"
>
<tr>
<th style="width: 20%;">Name</th>
<th style="width: 20%;">Email</th>
<th style="width: 50%;">Ticket</th>
<th style="width: 10%;">Registered</th>
</tr>
<tbody id="ticketwaves"></tbody>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</center>
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</body>
<script>
window.user_ev = {{ user_ev | tojson | safe }}
window.user_ev_sold = {{ user_ev_sold | tojson | safe }}
console.log(user_ev)
console.log(user_ev_sold)
function drawChart(user_ev_sold) {
var transactionsHTML = ''
for (var i = 0; i < user_ev_sold.length; i++) {
var ev = user_ev_sold[i]
transactionsHTML =
"<tr><td>" +
ev.name +
'</td><td>' +
'<button style="background-color: #333646;padding: 0;border: none;background: none;" class="lost"><span class="txt">xxxxxx</span>' +
'<span class="ema">' + ev.email + '</span></button>'+
'</td><td>' +
ev.hash +
'</td><td>' +
ev.reg +
'</td></tr>' +
transactionsHTML
document.getElementById('ticketwaves').innerHTML = transactionsHTML
}
}
if (user_ev_sold.length) {
drawChart(user_ev_sold)
}
function postAjax(url, data, thekey, success) {
var params =
typeof data == 'string'
? data
: Object.keys(data)
.map(function(k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k])
})
.join('&')
var xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('POST', url)
xhr.onreadystatechange = function() {
if (xhr.readyState > 3 && xhr.status == 200) {
success(xhr.responseText)
}
}
xhr.setRequestHeader('X-Api-Key', thekey)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(params)
return xhr
}
function getAjax(url, thekey, success) {
var xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP')
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (xhr.readyState > 3 && xhr.status == 200) {
success(xhr.responseText)
}
}
xhr.setRequestHeader('X-Api-Key', thekey)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send()
return xhr
}
function scanQRsend() {
document.getElementById('scantickets').innerHTML =
"<div class='modal-content'>"+
"<br/><div id='registered'><div id='loadingMessage'>🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>" +
"<canvas id='canvas' hidden></canvas><div id='output' hidden><div id='outputMessage'></div>" +
"<br/><span id='outputData'></span></div></div><div class='modal-footer'>"+
"<button type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel</button><br/><br/></div>"
var video = document.createElement('video')
var canvasElement = document.getElementById('canvas')
var canvas = canvasElement.getContext('2d')
var loadingMessage = document.getElementById('loadingMessage')
var outputContainer = document.getElementById('output')
var outputMessage = document.getElementById('outputMessage')
var outputData = document.getElementById('outputData')
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices
.getUserMedia({video: {facingMode: 'environment'}})
.then(function(stream) {
video.srcObject = stream
video.setAttribute('playsinline', true) // required to tell iOS safari we don't want fullscreen
video.play()
requestAnimationFrame(tick)
})
function tick() {
loadingMessage.innerText = '⌛ Loading video...'
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true
canvasElement.hidden = false
outputContainer.hidden = false
canvasElement.height = video.videoHeight
canvasElement.width = video.videoWidth
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height)
var imageData = canvas.getImageData(
0,
0,
canvasElement.width,
canvasElement.height
)
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert'
})
if (code) {
thehash = code.data
document.getElementById('registered').innerHTML = "<h1 style='color:green;font-size:300%;'>Registered!</h1>"
outputMessage.hidden = true
outputData.parentElement.hidden = false
outputData.innerText = JSON.stringify(code.data)
getAjax("{{ url_for('events.api_checkticket') }}?thehash=" + thehash, "filla", function(datab) {
if (JSON.parse(datab).status == 'TRUE') {
location.reload()
}
})
} else {
outputMessage.hidden = false
outputData.parentElement.hidden = true
}
}
requestAnimationFrame(tick)
}
}
function cancelsend() {
location.reload();
}
function getUrlVars() {
var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
});
return vars;
}
var name = getUrlVars()["name"];
var thehash = getUrlVars()["thehash"];
console.log(thehash)
if(thehash != null){
document.getElementById('qrcodetxt').innerHTML = "<center><h1>" + name + " is registered!</h1></center>"
}
</script>
</html>

View File

@ -1,457 +1,33 @@
<!-- @format -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>LNBits Wallet</title>
<meta
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
name="viewport"
/>
<!-- Bootstrap 3.3.2 -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"
/>
<!-- FontAwesome 4.3.0 -->
<link
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Ionicons 2.0.0 -->
<link
href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet"
type="text/css"
/>
<!-- Theme style -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/>
<!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/>
<!-- Morris chart -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/morris/morris.css') }}"
/>
<!-- jvectormap -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}"
/>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<style>
.small-box > .small-box-footer {
text-align: left;
padding-left: 10px;
}
#loadingMessage {
text-align: center;
padding: 40px;
background-color: #eee;
}
#canvas {
width: 100%;
}
#output {
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
}
#output div {
padding-bottom: 10px;
word-wrap: break-word;
}
#noQRFound {
text-align: center;
}
</style>
<!-- jQuery 2.1.3 -->
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 -->
<script
src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript"
></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
<script>
$.widget.bridge('uibutton', $.ui.button)
</script>
<!-- Bootstrap 3.3.2 JS -->
<script
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"
type="text/javascript"
></script>
<!-- Morris.js charts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript"
></script>
<!-- Sparkline -->
<script
src="{{ url_for('static', filename='plugins/sparkline/jquery.sparkline.min.js') }}"
type="text/javascript"
></script>
<!-- jvectormap -->
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"
type="text/javascript"
></script>
<!-- jQuery Knob Chart -->
<script
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 -->
<script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll -->
<script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
type="text/javascript"
></script>
<!-- FastClick -->
<script src="{{ url_for('static', filename='plugins/fastclick/fastclick.min.js') }}"></script>
<!-- AdminLTE App -->
<script
src="{{ url_for('static', filename='dist/js/app.min.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
<script
src="{{ url_for('static', filename='dist/js/pages/dashboard.js') }}"
type="text/javascript"
></script>
<!-- AdminLTE for demo purposes -->
<script
src="{{ url_for('static', filename='dist/js/demo.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/datatables/jquery.dataTables.js') }}"
type="text/javascript"
></script>
<link
rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<script
src="{{ url_for('static', filename='plugins/jscam/JS.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/jscam/qrcode.min.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/decoder.js') }}"
type="text/javascript"
></script>
<script
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
type="text/javascript"
></script>
<style>
//GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper {
background: #1f2234;
}
body {
color: #fff;
}
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #1f2234;
border-left-color: #8964a9;
}
.skin-blue .main-header .navbar {
background-color: #2e507d;
}
.content-wrapper,
.right-side {
background-color: #1f2234;
}
.skin-blue .main-header .logo {
background-color: #1f2234;
color: #fff;
}
.skin-blue .sidebar-menu > li.header {
color: #4b646f;
background: #1f2234;
}
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
background: #1f2234;
}
.skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background: #1f2234;
}
.skin-blue .sidebar-menu > li > a {
border-left: 3px solid transparent;
margin-right: 1px;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
color: #fff;
background: #3e355a;
border-left-color: #8964a9;
}
.skin-blue .main-header .logo:hover {
background: #3e355a;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color: #3e355a;
}
.main-footer {
background-color: #1f2234;
padding: 15px;
color: #fff;
border-top: 0px;
}
.skin-blue .main-header .navbar {
background-color: #1f2234;
}
.bg-red,
.callout.callout-danger,
.alert-danger,
.alert-error,
.label-danger,
.modal-danger .modal-body {
background-color: #1f2234 !important;
}
.alert-danger,
.alert-error {
border-color: #fff;
border: 1px solid #fff;
border-radius: 7px;
}
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus {
color: #f6f6f6;
background-color: #3e355a;
}
.bg-aqua,
.callout.callout-info,
.alert-info,
.label-info,
.modal-info .modal-body {
background-color: #3e355a !important;
}
.box {
position: relative;
border-radius: 3px;
background-color: #333646;
border-top: 3px solid #8964a9;
margin-bottom: 20px;
width: 100%;
}
.table-striped > tbody > tr:nth-of-type(2n + 1) {
background-color: #333646;
}
.box-header {
color: #fff;
}
.box.box-danger {
border-top-color: #8964a9;
}
.box.box-primary {
border-top-color: #8964a9;
}
a {
color: #8964a9;
}
.box-header.with-border {
border-bottom: none;
}
a:hover,
a:active,
a:focus {
outline: none;
text-decoration: none;
color: #fff;
}
// .modal.in .modal-dialog{
// color:#000;
// }
.form-control {
background-color: #333646;
color: #fff;
}
.box-footer {
border-top: none;
background-color: #333646;
}
.modal-footer {
border-top: none;
}
.modal-content {
background-color: #333646;
}
.modal.in .modal-dialog {
background-color: #333646;
}
.layout-boxed {
background: none;
background-color: rgba(0, 0, 0, 0);
background-color: #3e355a;
}
.skin-blue .sidebar-menu > li > a:hover,
.skin-blue .sidebar-menu > li.active > a {
background: none;
}
</style>
</head>
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="{{ url_for('core.home') }}" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a
href="#"
class="sidebar-toggle"
data-toggle="offcanvas"
role="button"
>
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
</div>
</nav>
</header>
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar" style="height: auto;"></section>
<!-- /.sidebar -->
</aside>
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
LNBits Events
<small>Lightning powered tickets</small>
</h1>
</section>
<!-- Main content -->
<section class="content">
<br /><br />
<center>
<h2 style="width: 70%; font-size: 400%;">
Bookmark/Screenshot this page. <br />It is your ticket!
</h2>
</center>
<center>
<div
style="width: 340px; background-color: white; padding: 20px;"
id="qrcode"
></div>
</center>
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</body>
<script>
new QRCode(document.getElementById('qrcode'), {
text: '{{ticket}}',
width: 300,
height: 300,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
})
</script>
</html>
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<center>
<h3 class="q-my-none">{{ ticket_name }} Ticket</h3>
<br />
<h5 class="q-my-none">
Bookmark or screenshot this page,<br />
and present it for registration!
</h5>
<br />
<qrcode
:value="{{ ticket_id }}"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
</center>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin]
})
</script>
{% endblock %}

View File

@ -1,174 +1,45 @@
import uuid
from flask import g, abort, render_template
from datetime import date, datetime
from flask import jsonify, render_template, request, redirect, url_for
from datetime import datetime
from lnbits.decorators import check_user_exists, validate_uuids
from http import HTTPStatus
from lnbits.db import open_db, open_ext_db
from lnbits.extensions.events import events_ext
from .crud import get_ticket, get_event
@events_ext.route("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
def index():
"""Main events link page."""
usr = request.args.get("usr")
return render_template("events/index.html", user=g.user)
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
# Get all the data
with open_db() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
user_ext = db.fetchall("SELECT extension FROM extensions WHERE user = ? AND active = 1", (usr,))
user_ext = [v[0] for v in user_ext]
@events_ext.route("/<event_id>")
def display(event_id):
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
if event.amount_tickets < 1:
return render_template("events/error.html", event_name=event.name, event_error="Sorry, tickets are sold out :(")
datetime_object = datetime.strptime(event.closing_date, '%Y-%m-%d').date()
if date.today() > datetime_object:
return render_template("events/error.html", event_name=event.name, event_error="Sorry, ticket closing date has passed :(")
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
# If del is selected by user from events page, the event link is to be deleted
evdel = request.args.get("del")
if evdel:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE uni = ?", (evdel,))
events_ext_db.execute("DELETE FROM events WHERE uni = ?", (evdel,))
if user_ev[0][9] > 0:
events_ext_db.execute("DELETE FROM eventssold WHERE uni = ?", (user_ev[0][12],))
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
print(user_ext)
return render_template("events/display.html", event_id=event_id, event_name=event.name, event_info=event.info, event_price=event.price_per_ticket)
return render_template(
"events/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_ev=user_ev
)
@events_ext.route("/create", methods=["GET", "POST"])
def create():
"""."""
@events_ext.route("/ticket/<ticket_id>")
def ticket(ticket_id):
ticket = get_ticket(ticket_id) or abort(HTTPStatus.NOT_FOUND, "Ticket does not exist.")
event = get_event(ticket.event) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
return render_template("events/ticket.html", ticket_id=ticket_id, ticket_name=event.name, ticket_info=event.info)
data = request.json
tit = data["tit"]
wal = data["wal"]
cldate = data["cldate"]
notickets = data["notickets"]
prtick = data["prtickets"]
usr = data["usr"]
descr = data["descr"]
wall = wal.split("-")
# Form validation
if (
not tit.replace(" ", "").isalnum()
or wal == ""
or int(notickets) < 0
or int(prtick) < 0
):
return jsonify({"ERROR": "FORM ERROR"}), 401
# If id that means its a link being edited, delete the record first
if "id" in data:
unid = data["id"].split("-")
uni = unid[1]
unireg = unid[2]
with open_ext_db("events") as events_ext_db:
events_ext_db.execute("DELETE FROM events WHERE uni = ?", (unid[1],))
else:
uni = uuid.uuid4().hex
unireg = uuid.uuid4().hex
@events_ext.route("/register/<event_id>")
def register(event_id):
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
with open_db() as dbb:
user_wallets = dbb.fetchall("SELECT * FROM wallets WHERE user = ? AND id = ?", (usr, wall[1],))
if not user_wallets:
return jsonify({"ERROR": "NO WALLET USER"}), 401
with open_db() as db:
user_ext = db.fetchall("SELECT * FROM extensions WHERE user = ?", (usr,))
user_ext = [v[0] for v in user_ext]
# Add to DB
with open_ext_db("events") as events_ext_db:
events_ext_db.execute(
"""
INSERT OR IGNORE INTO events
(usr, wal, walnme, walinvkey, uni, tit, cldate, notickets, prtick, descr, unireg)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
usr,
wall[1],
user_wallets[0][1],
user_wallets[0][4],
uni,
tit,
cldate,
notickets,
prtick,
descr,
unireg,
),
)
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE usr = ?", (usr,))
if not user_ev:
return jsonify({"ERROR": "NO WALLET USER"}), 401
return render_template(
"events/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_ev=user_ev
)
return render_template("events/register.html", event_id=event_id, event_name=event.name, wallet_id=event.wallet)
@events_ext.route("/wave/<wave>/", methods=["GET", "POST"])
def wave(wave):
"""."""
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE unireg = ?", (wave,))
if not user_ev:
return jsonify({"ERROR": "NO RECORD"}), 401
return render_template(
"events/display.html", wave=wave, nme=user_ev[0][6], descr=user_ev[0][11]
)
@events_ext.route("/registration/<wave>", methods=["GET", "POST"])
def registration(wave):
"""."""
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE uni = ?", (wave,))
user_ev_sold = events_ext_db.fetchall("SELECT * FROM eventssold WHERE uni = ? AND paid = 1", (user_ev[0][12],))
if not user_ev:
return jsonify({"ERROR": "NO RECORD"}), 401
return render_template(
"events/registration.html", user_ev=user_ev, user_ev_sold=user_ev_sold
)
@events_ext.route("/ticket/", methods=["GET"])
def ticket():
"""."""
thehash = request.args.get("hash")
unireg = request.args.get("unireg")
#Double check the payment has cleared
with open_db() as db:
payment = db.fetchall("SELECT * FROM apipayments WHERE payhash = ?", (thehash,))
if not payment:
return jsonify({"status": "ERROR", "reason":"NO RECORD OF PAYMENT"}), 400
if payment[0][4] == 1:
return jsonify({"status": "ERROR", "reason":"NOT PAID"}), 400
#Update databases
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE unireg = ?", (unireg,))
updatesold = user_ev[0][9] + 1
events_ext_db.execute("UPDATE events SET sold = ? WHERE unireg = ?", (updatesold, unireg,))
events_ext_db.execute("UPDATE eventssold SET paid = 1 WHERE hash = ?", (thehash,))
eventssold = events_ext_db.fetchall("SELECT * FROM eventssold WHERE hash = ?", (thehash,))
if not eventssold:
return jsonify({"status": "ERROR", "reason":"NO TICKET RECORD"}), 200
return render_template(
"events/ticket.html", name=eventssold[0][3], ticket=thehash
)

View File

@ -1,65 +1,155 @@
import uuid
import json
import requests
from flask import g, jsonify, request
from http import HTTPStatus
from flask import jsonify, request, url_for
from datetime import datetime
from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import create_invoice
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.settings import WALLET
from lnbits.db import open_db, open_ext_db
from lnbits.extensions.events import events_ext
@events_ext.route("/api/v1/getticket/", methods=["GET","POST"])
def api_getticket():
"""."""
data = request.json
unireg = data["unireg"]
name = data["name"]
email = request.args.get("ema")
with open_ext_db("events") as events_ext_db:
user_ev = events_ext_db.fetchall("SELECT * FROM events WHERE unireg = ?", (unireg,))
from .crud import create_ticket, get_ticket, get_tickets, delete_ticket, create_event, update_event, get_event, get_events, delete_event, get_event_tickets
header = {"Content-Type": "application/json", "X-Api-Key": user_ev[0][4]}
data = {"value": str(user_ev[0][10]), "memo": user_ev[0][6]}
print(url_for("api_invoices", _external=True))
r = requests.post(url=url_for("api_invoices", _external=True), headers=header, data=json.dumps(data))
r_json = r.json()
#########Events##########
if "ERROR" in r_json:
return jsonify({"status": "ERROR", "reason": r_json["ERROR"]}), 400
events_ext_db.execute(
"""
INSERT OR IGNORE INTO eventssold
(uni, email, name, hash)
VALUES (?, ?, ?, ?)
""",
(
unireg,
email,
name,
r_json["payment_hash"]
),
@events_ext.route("/api/v1/events", methods=["GET"])
@api_check_wallet_key("invoice")
def api_events():
wallet_ids = [g.wallet.id]
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
return jsonify([event._asdict() for event in get_events(wallet_ids)]), HTTPStatus.OK
@events_ext.route("/api/v1/events", methods=["POST"])
@events_ext.route("/api/v1/events/<event_id>", methods=["PUT"])
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"wallet": {"type": "string", "empty": False, "required": True},
"name": {"type": "string", "empty": False, "required": True},
"info": {"type": "string", "min": 0, "required": True},
"closing_date": {"type": "string", "empty": False, "required": True},
"event_start_date": {"type": "string", "empty": False, "required": True},
"event_end_date": {"type": "string", "empty": False, "required": True},
"amount_tickets": {"type": "integer", "min": 0, "required": True},
"price_per_ticket": {"type": "integer", "min": 0, "required": True}
}
)
def api_event_create(event_id=None):
if event_id:
event = get_event(event_id)
print(g.data)
if not event:
return jsonify({"message": "Form does not exist."}), HTTPStatus.NOT_FOUND
if event.wallet != g.wallet.id:
return jsonify({"message": "Not your event."}), HTTPStatus.FORBIDDEN
event = update_event(event_id, **g.data)
else:
event = create_event(**g.data)
print(event)
return jsonify(event._asdict()), HTTPStatus.CREATED
@events_ext.route("/api/v1/events/<event_id>", methods=["DELETE"])
@api_check_wallet_key("invoice")
def api_form_delete(event_id):
event = get_event(event_id)
if not event:
return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND
if event.wallet != g.wallet.id:
return jsonify({"message": "Not your event."}), HTTPStatus.FORBIDDEN
delete_event(event_id)
return "", HTTPStatus.NO_CONTENT
#########Tickets##########
@events_ext.route("/api/v1/tickets", methods=["GET"])
@api_check_wallet_key("invoice")
def api_tickets():
wallet_ids = [g.wallet.id]
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
return jsonify([ticket._asdict() for ticket in get_tickets(wallet_ids)]), HTTPStatus.OK
@events_ext.route("/api/v1/tickets/<event_id>/<sats>", methods=["GET"])
def api_ticket_create(event_id, sats):
event = get_event(event_id)
try:
checking_id, payment_request = create_invoice(
wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {event_id}"
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"status": "TRUE", "pay_req": r_json["pay_req"], "payment_hash": r_json["payment_hash"]}), 200
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.OK
@events_ext.route("/api/v1/checkticket/", methods=["GET"])
def api_checkticket():
"""."""
thehash = request.args.get("thehash")
#Check databases
with open_ext_db("events") as events_ext_db:
eventssold = events_ext_db.fetchall("SELECT * FROM eventssold WHERE hash = ?", (thehash,))
if not eventssold:
return jsonify({"status": "ERROR", "reason":"NO TICKET RECORD"}), 200
if eventssold[0][4] == 0:
return jsonify({"status": "ERROR", "reason":"NOT PAID"}), 200
with open_ext_db("events") as events_ext_db:
events_ext_db.execute("UPDATE eventssold SET reg = 1 WHERE hash = ?", (thehash,))
@events_ext.route("/api/v1/tickets/<checking_id>", methods=["POST"])
@api_validate_post_request(
schema={
"event": {"type": "string", "empty": False, "required": True},
"name": {"type": "string", "empty": False, "required": True},
"email": {"type": "string", "empty": False, "required": True}
})
def api_ticket_send_ticket(checking_id):
event = get_event(g.data['event'])
if not event:
return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND
try:
is_paid = not WALLET.get_invoice_status(checking_id).pending
except Exception:
return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND
if is_paid:
wallet = get_wallet(event.wallet)
payment = wallet.get_payment(checking_id)
payment.set_pending(False)
ticket = create_ticket(wallet=event.wallet, **g.data)
return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK
return jsonify({"paid": False}), HTTPStatus.OK
@events_ext.route("/api/v1/tickets/<ticket_id>", methods=["DELETE"])
@api_check_wallet_key("invoice")
def api_ticket_delete(ticket_id):
ticket = get_ticket(ticket_id)
if not ticket:
return jsonify({"message": "Ticket does not exist."}), HTTPStatus.NOT_FOUND
if ticket.wallet != g.wallet.id:
return jsonify({"message": "Not your ticket."}), HTTPStatus.FORBIDDEN
delete_ticket(ticket_id)
return "", HTTPStatus.NO_CONTENT
#########EventTickets##########
@events_ext.route("/api/v1/eventtickets/<wallet_id>/<event_id>", methods=["GET"])
def api_event_tickets(wallet_id, event_id):
return jsonify([ticket._asdict() for ticket in get_event_tickets(wallet_id=wallet_id, event_id=event_id)]), HTTPStatus.OK
return jsonify({"status": "TRUE", "name": eventssold[0][3]}), 200