ticket gen working + various changes

This commit is contained in:
2023-08-07 12:24:52 +02:00
parent 5cbf624284
commit ed38edd880
24 changed files with 211 additions and 98 deletions

7
.gitignore vendored
View File

@@ -17,3 +17,10 @@ node_modules
/dist
*.secret.json
# ignore all latex files except .tex
*.aux
*.fls
*.synctex.gz
*.fdb_latexmk
._wordcount_selection.tex

View File

@@ -0,0 +1,3 @@
# Ticket Template
This template can be used by libreevent if you do not edit the template. It is entirely copyleft and you can change it to your liking. The LaTex document is included with libreevent.

Binary file not shown.

View File

@@ -0,0 +1,25 @@
\documentclass[11pt]{article}
% Imports %
\usepackage{amsmath}
\usepackage{graphicx}
\usepackage{subcaption}
\usepackage[export]{adjustbox}
\usepackage{tcolorbox}
\usepackage{xcolor}
\usepackage[utf8]{inputenc}
\usepackage[a4paper]{geometry}
\addtolength{\oddsidemargin}{-0.5in}
\addtolength{\textwidth}{1in}
\addtolength{\evensidemargin}{-0.55in}
\addtolength{\topmargin}{-0.75in}
\addtolength{\textheight}{1.5in}
\begin{document}
\begin{tcolorbox}[colback=gray!5!white,colframe=black!75!black,title=Your Ticket - libreevent event management solution]
\vspace{6cm}
\end{tcolorbox}
\end{document}

View File

@@ -28,10 +28,9 @@ module.exports = ( app, settings ) => {
if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail;
if ( data ) {
if ( data.status ) {
request.session.username = request.body.mail;
// TODO: Check if user has 2fa enabled
if ( settings.twoFA === 'standard' ) {
if ( data.twoFA === 'simple' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ];
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
@@ -40,7 +39,7 @@ module.exports = ( app, settings ) => {
request.session.token = tok;
response.send( { 'status': '2fa' } );
} )();
} else if ( settings.twoFA === 'enhanced' ) {
} else if ( data.twoFA === 'enhanced' ) {
( async () => {
let res = twoFA.registerEnhancedAuthentication();
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
@@ -50,7 +49,7 @@ module.exports = ( app, settings ) => {
response.send( { 'status': '2fa+', 'code': res.code } );
} )();
} else {
request.session.loggedInUser = true;
request.session.loggedInAdmin = true;
response.send( { 'status': 'ok' } );
}
} else {
@@ -63,7 +62,6 @@ module.exports = ( app, settings ) => {
} );
app.get( '/admin/2fa', ( request, response ) => {
// TODO: Add multi language
let tokType = twoFA.verifySimple( request.query.token );
if ( tokType === 'standard' ) {
request.session.loggedInAdmin = true;

View File

@@ -42,7 +42,13 @@ class GETHandler {
} );
} else if ( call === 'getLocations' ) {
db.getJSONData( 'locations' ).then( data => {
resolve( data );
resolve( data ?? {} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );
} else if ( call === 'getTicketTemplate' ) {
db.getJSONDataSimple( 'tickets', query.ticket ).then( data => {
resolve( data ?? {} );
} ).catch( error => {
reject( { 'code': 500, 'error': error } );
} );

View File

@@ -24,7 +24,7 @@ module.exports.checkpassword = ( username, password ) => {
if ( data ) {
if ( data[ 0 ] ) {
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
resolve( res );
resolve( { 'status': res, 'twoFA': data[ 0 ].two_fa } );
} );
} else {
resolve( false );

View File

@@ -101,6 +101,6 @@ app.use( ( request, response ) => {
console.log( '\n\n[ Server ] loading complete!\n\n' );
const PORT = process.env.PORT || 8081;
const PORT = process.env.PORT || 8080;
console.log( '[ Server ] listening on port ' + PORT );
http.createServer( app ).listen( PORT );

View File

@@ -30,7 +30,7 @@ class GETHandler {
if ( query.event ) {
db.getJSONDataSimple( 'booked', query.event ).then( data => {
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
resolve( { 'booked': data ? data.booked : {}, 'user': dat[ 0 ] ? JSON.parse( dat[ 0 ].data )[ query.event ] ?? {} : {} } );
resolve( { 'booked': data ?? {}, 'user': dat[ 0 ] ? JSON.parse( dat[ 0 ].data )[ query.event ] ?? {} : {} } );
} );
} ).catch( error => {
reject( { 'code': 500, 'message': error } );

View File

@@ -39,7 +39,9 @@ class POSTHandler {
return;
}
transmit[ data.eventID ][ data.id ] = data;
// TODO: Prevent seat selection if already taken (also if in booked!)
// TODO: Respect max ticket count per user
// TODO: maybe move to per event setting
let totalUserTickets = 0;
for ( let event in transmit ) {
for ( let ticket in transmit[ event ] ) {

File diff suppressed because one or more lines are too long

View File

@@ -58,6 +58,16 @@ module.exports.writeDataSimple = ( db, column, searchQuery, data ) => {
} );
};
module.exports.deleteDataSimple = ( db, column, searchQuery ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'deleteData', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( dat => {
resolve( dat );
} ).catch( error => {
reject( error );
} );
} );
};
module.exports.checkDataAvailability = ( db, column, searchQuery ) => {
return new Promise( ( resolve, reject ) => {
dbh.query( { 'command': 'checkDataAvailability', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( res => {

View File

@@ -55,7 +55,7 @@ class SQLDB {
if ( error ) throw error;
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) return 'DB HAS TO USE InnoDB!';
} );
this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing_ok TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
this.sqlConnection.query( 'CREATE TABLE libreevent_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, pass TEXT, name TEXT, first_name TEXT, two_fa TINYTEXT, user_data VARCHAR( 60000 ), mail_confirmed TINYTEXT, marketing TINYTEXT, PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
this.sqlConnection.query( 'CREATE TABLE libreevent_orders ( order_id INT ( 10 ) NOT NULL AUTO_INCREMENT, order_name TINYTEXT, account_id INT ( 10 ) NOT NULL, tickets VARCHAR( 60000 ), processed TINYTEXT, PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
@@ -104,6 +104,10 @@ class SQLDB {
- addData:
- operation.data (key-value pair with all data as values and column to insert into as key)
- deleteData:
- operation.property (the column to search for the value)
- operation.searchQuery (the value to search for [will be sanitised by method])
- updateData:
- operation.newValues (a object with keys being the column and value being the value to be inserted into that column, values are being
sanitised by the function)

View File

@@ -40,6 +40,7 @@ module.exports = ( app, settings ) => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
console.log( data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
@@ -116,7 +117,7 @@ module.exports = ( app, settings ) => {
}
} );
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), ( req, res ) => {
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), async ( req, res ) => {
const payload = req.body;
const sig = req.headers[ 'stripe-signature' ];
@@ -139,9 +140,26 @@ module.exports = ( app, settings ) => {
db.getDataSimple( 'users', 'email', sessionReference[ event.data.object.id ][ 'email' ] ).then( user => {
if ( user[ 0 ] ) {
console.log( sessionReference[ event.data.object.id ][ 'tok' ] );
const tickets = JSON.parse( dat[ 0 ].data );
db.writeDataSimple( 'orders', 'account_id', user[ 0 ].account_id, { 'account_id': user[ 0 ].account_id, 'tickets': dat[ 0 ].data, 'order_name': sessionReference[ event.data.object.id ][ 'tok' ] } ).then( () => {
TicketGenerator.generateTickets( sessionReference[ event.data.object.id ] );
} );
db.getJSONData( 'booked' ).then( ret => {
let booked = ret ?? {};
for ( let event in tickets ) {
if ( !booked[ String( event ) ] ) {
booked[ String( event ) ] = {};
}
for ( let tik in tickets[ event ] ) {
booked[ event ][ tik ] = tickets[ event ][ tik ];
}
}
db.writeJSONData( 'booked', booked );
} );
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).catch( error => {
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
} );
} else {
console.log( sessionReference[ event.data.object.id ][ 'email' ] );
console.error( 'user not found' );

View File

@@ -35,7 +35,7 @@ class TicketGenerator {
this.runningTickets = {};
}
// TODO: Save to disk in case of crash of server / reboot / whatever
// TODO: Save to disk / DB in case of crash of server / reboot / whatever
// and continue processing once back online
generateTickets ( order ) {
this.ticketQueue[ this.jobId ] = { 'order': order };
@@ -114,15 +114,18 @@ class TicketGenerator {
for ( let event in order ) {
const template = this.tickets[ event ];
for ( let ticket in order[ event ] ) {
const data = [ {
'locationAndTime': this.events[ event ][ 'date' ],
'ticketName': order[ event ][ ticket ][ 'name' ],
'ticketQRCode': ord[ 0 ].order_name + '_' + order[ event ][ ticket ][ 'id' ],
} ];
const page = await pdfLib.PDFDocument.load( await pdfme.generate( { 'template': template, 'inputs': data } ) );
const p = await doc.copyPages( page, page.getPageIndices() );
pages.push( p );
p.forEach( ( page ) => doc.addPage( page ) );
for ( let tik = 0; tik < ( order[ event ][ ticket ].count ?? 1 ); tik++ ) {
const data = [ {
'eventName': this.events[ event ][ 'name' ],
'locationAndTime': new Date( this.events[ event ][ 'date' ] ).toLocaleString(),
'ticketName': order[ event ][ ticket ][ 'name' ],
'ticketQRCode': ord[ 0 ].order_name + '_' + order[ event ][ ticket ][ 'id' ],
} ];
const page = await pdfLib.PDFDocument.load( await pdfme.generate( { 'template': template, 'inputs': data } ) );
const p = await doc.copyPages( page, page.getPageIndices() );
pages.push( p );
p.forEach( ( page ) => doc.addPage( page ) );
}
}
}
const f = path.join( __dirname + '/store/' + ord[ 0 ].order_name + '.pdf' );

View File

@@ -49,8 +49,8 @@ module.exports = ( app, settings ) => {
app.post( '/user/login', bodyParser.json(), ( request, response ) => {
if ( request.body.mail && request.body.password ) {
pwdmanager.checkpassword( request.body.mail, request.body.password ).then( data => {
request.session.username = request.body.mail;
if ( data.status ) {
request.session.username = request.body.mail;
if ( data.twoFA === 'simple' ) {
( async () => {
let tok = twoFA.registerStandardAuthentication()[ 'token' ];
@@ -83,7 +83,6 @@ module.exports = ( app, settings ) => {
} );
app.get( '/user/2fa', ( request, response ) => {
// TODO: Add multi language
let tokType = twoFA.verifySimple( request.query.token );
if ( tokType === 'standard' ) {
request.session.loggedInUser = true;
@@ -154,7 +153,7 @@ module.exports = ( app, settings ) => {
mailManager.sendMail( request.body.mail, await twoFA.generateSignupEmail( tok, settings.yourDomain, settings.name ), 'Confirm your email', settings.mailSender );
} )();
pwdmanager.hashPassword( request.body.password ).then( hash => {
db.writeDataSimple( 'users', 'email', request.body.mail, { 'email': request.body.mail, 'pass': hash, 'first_name': request.body.firstName, 'name': request.body.name, 'two_fa': 'disabled', 'user_data': JSON.stringify( { 'country': request.body.country } ) } ).then( () => {
db.writeDataSimple( 'users', 'email', request.body.mail, { 'email': request.body.mail, 'pass': hash, 'first_name': request.body.firstName, 'name': request.body.name, 'two_fa': 'disabled', 'user_data': JSON.stringify( { 'country': request.body.country } ), 'marketing': request.body.newsletter ? generator.generateToken( 60 ) : null } ).then( () => {
request.session.loggedInUser = true;
request.session.username = request.body.mail;
response.send( 'ok' );

View File

@@ -5,6 +5,6 @@
"db": "mysql",
"payments": "stripe",
"name": "libreevent",
"yourDomain": "http://localhost:8081",
"yourDomain": "http://localhost:8080",
"mailSender": "libreevent <info@libreevent.janishutz.com>"
}

View File

@@ -3,11 +3,13 @@
- make pricing groups changeable in UI (event categories)
- create function that parses DB every 15 minutes and clears out junk
- create function that parses DB every 15 minutes and clears out junk --> Also update data in db when user goes to purchase to prevent clearing during purchase
- Require user to confirm email before purchasing
- Guest purchase in the future (remove from matura shit)
- Guest purchase in the future (maybe remove from matura)
- Create password changing endpoint (to reset forgotten pwd)
- Add Admin profile (page to change account settings per person like changing pwd)
@@ -19,7 +21,9 @@
- Implement Permission system
- Seat numbering
- Seat numbering!!
- Add localization for date
- add webpack (or any other minifying tool) to project website to decrease file size (OPTIONAL)

View File

@@ -169,7 +169,7 @@
} );
if ( !sessionStorage.getItem( 'seatplan-history' ) ) {
sessionStorage.setItem( 'seatplaTODO:n-history', JSON.stringify( { '1': this.scaleDown( this.draggables ) } ) );
sessionStorage.setItem( 'seatplan-history', JSON.stringify( { '1': this.scaleDown( this.draggables ) } ) );
}
let history = sessionStorage.getItem( 'seatplan-history' ) ? JSON.parse( sessionStorage.getItem( 'seatplan-history' ) ) : {};

View File

@@ -9,7 +9,6 @@
<template>
<div id="window">
<h2>Seat plan: {{ event.name }}</h2>
<div class="parent" id="parent" @wheel="( e ) => { handleScroll( e ); }" @mousemove="( e ) => { handleDrag( e ); }" @mousedown="( e ) => { setOffset( e ); }">
<div class="content-parent">
<Vue3DraggableResizable v-for="draggable in draggables" :initW="draggable.w" :initH="draggable.h" :x="draggable.x" :y="draggable.y" :w="draggable.w" :h="draggable.h"
@@ -42,7 +41,7 @@
<button title="Reset zoom [=]" @click="zoom( 1 );"><span class="material-symbols-outlined">center_focus_strong</span></button>
<button title="Zoom out [-]" @click="zoom( -0.2 )"><span class="material-symbols-outlined">zoom_out</span></button>
</div>
<sideCartView :cart="cart" ref="cart"></sideCartView>
<sideCartView :cart="cart" :name="event.name" ref="cart"></sideCartView>
<notifications ref="notification" location="topright"></notifications>
<popups ref="popups" size="normal" @data="data => { reserveTicket( data ) }"
@ticket="data => { standingTicketHandling( data ) }"></popups>
@@ -156,7 +155,6 @@
}
this.seatChecks();
// TODO: Optimise for odd screen sizes and aspect ratios and fucking webkit
// TODO: Trim scroll box to about 200px more than seatplan size
sessionStorage.setItem( 'seatplan', JSON.stringify( this.scaleDown( this.draggables ) ) );
window.addEventListener( 'visibilitychange', ( e ) => {
@@ -171,15 +169,6 @@
if ( res.status === 200 ) {
let unavailableSeats = {};
res.json().then( data => {
for ( let seat in data.booked ) {
if ( data.booked[ seat ] ) {
if ( !unavailableSeats[ data.booked[ seat ].component ] ) {
unavailableSeats[ data.booked[ seat ].component ];
}
unavailableSeats[ data.booked[ seat ].component ][ data.booked[ seat ].id ] = 'nav';
}
}
for ( let seat in data.reserved ) {
if ( data.reserved[ seat ] ) {
if ( !unavailableSeats[ data.reserved[ seat ].component ] ) {
@@ -198,6 +187,15 @@
}
}
for ( let seat in data.booked ) {
if ( data.booked[ seat ] ) {
if ( !unavailableSeats[ data.booked[ seat ].component ] ) {
unavailableSeats[ data.booked[ seat ].component ];
}
unavailableSeats[ data.booked[ seat ].component ][ data.booked[ seat ].id ] = 'nav';
}
}
let tickets = {};
if ( this.cart[ this.event.eventID ] ) {
tickets = this.cart[ this.event.eventID ][ 'tickets' ];
@@ -449,8 +447,7 @@
if ( data.data[ group ] > 0 ) {
const options = {
method: 'post',
// TODO: Add correct name here as well once it is working at all
body: JSON.stringify( { 'id': 'ticket' + data.component + '_' + group, 'component': data.component, 'ticketOption': '', 'eventID': this.event.eventID, 'count': data.data[ group ], 'category': this.draggables[ data.component ].category, 'name': 'Ticket ' } ),
body: JSON.stringify( { 'id': 'ticket' + data.component + '_' + group, 'component': data.component, 'ticketOption': group, 'eventID': this.event.eventID, 'count': data.data[ group ], 'category': this.draggables[ data.component ].category, 'name': 'Ticket ' + data.component + ' (' + this.event.ageGroups[ group ].name + ')' } ),
headers: {
'Content-Type': 'application/json',
'charset': 'utf-8'
@@ -510,7 +507,7 @@
.parent {
height: 80vh;
width: 70vw;
top: 17vh;
top: 90px;
left: 5vw;
position: absolute;
border: black 1px solid;
@@ -548,7 +545,7 @@
.toolbar {
display: flex;
position: fixed;
top: 17vh;
top: 90px;
left: 5.5vw;
}
.toolbar button {

View File

@@ -9,6 +9,7 @@
<template>
<div id="sideCartView">
<h1>Seat plan: {{ name }}</h1>
<h2>Cart</h2>
<div v-if="Object.keys( cart ).length > 0" style="height: 100%; width: 100%;">
<div class="scroll-wrapper">
@@ -64,6 +65,10 @@ export default {
'height': {
type: Number,
default: 17
},
'name': {
type: String,
default: ''
}
},
data() {
@@ -92,7 +97,7 @@ export default {
position: fixed;
right: 0;
height: 100vh;
top: 17vh;
top: 90px;
width: 25vw;
background-color: var( --accent-background );
color: var( --secondary-color );

View File

@@ -21,7 +21,7 @@
</td>
</tr>
</table>
<p>Detailed explanation of payment gateways can be found <a href="https://libreevent.janishutz.com/docs/payments" target="_blank">here</a>. You may install more payment gateway integrations in the plugins section.</p>
<p>Detailed explanation of payment gateways can be found <a href="https://libreevent.janishutz.com/docs/payments" target="_blank">here</a>. You may install more payment gateway integrations in the plugins section. Only one may be used at any given time.</p>
<div class="admin-settings">
<h2>Admin Accounts</h2>
@@ -103,8 +103,8 @@
'value': 'stripe'
},
'adyen': {
'displayName':'Adyen',
'value': 'adyen'
'displayName':'Payrexx',
'value': 'payrexx'
},
}
},

File diff suppressed because one or more lines are too long

View File

@@ -43,6 +43,12 @@
<input type="password" v-model="formData[ 'password2' ]" name="password2" id="password2" required><br><br>
</td>
</tr>
<tr>
<td>
<label for="news">Do you want to potentially get newsletter?</label><br>
<input type="checkbox" v-model="formData[ 'newsletter' ]" name="news" id="news"><br><br>
</td>
</tr>
</table>
<!-- TODO: Ask for permission to send emails (Make question sound really optional) -->
</form>