ticket generation working

This commit is contained in:
2023-08-06 18:37:21 +02:00
parent 2812ab9055
commit 32ed36b93f
26 changed files with 594 additions and 96 deletions

View File

@@ -0,0 +1 @@
{}

View File

@@ -33,7 +33,6 @@ module.exports.getDataSimple = ( db, column, searchQuery ) => {
} ).catch( error => {
reject( error );
} );
// resolve( '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O' );
} );
};

View File

@@ -13,7 +13,7 @@
class DataHelper {
constructor () {
}
}

View File

@@ -7,8 +7,98 @@
*
*/
const fs = require( 'fs' );
const path = require( 'path' );
class JSONDB {
constructor () {
this.db = {};
}
connect ( ) {
this.db = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/db.json' ) ) );
setInterval( async () => {
fs.writeFile( path.join( __dirname + '/data/db.json' ), JSON.stringify( this.db ) );
}, 10000 );
console.log( '[ JSON-DB ] Database initialized successfully' );
return 'connection';
}
query ( operation, table ) {
return new Promise( ( resolve, reject ) => {
/*
Possible operation.command values (all need the table argument of the method call):
- getAllData: no additional instructions needed
- getFilteredData:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- InnerJoin (Select values that match in both tables):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- LeftJoin (Select values in first table and return all corresponding values of second table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- RightJoin (Select values in second table and return all corresponding values of first table):
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- operation.selection (The columns of both tables to be selected, e.g. users.name, orders.id)
- operation.secondTable (The second table to perform Join operation with)
- operation.matchingParam (Which properties should be matched to get the data, e.g. order.user_id=users.id)
- addData:
- operation.data (key-value pair with all data as values and column to insert into as key)
- 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)
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- checkDataAvailability:
- operation.property (the column to search for the value),
- operation.searchQuery (the value to search for [will be sanitised by method])
- fullCustomCommand:
- operation.query (the SQL instruction to be executed) --> NOTE: This command will not be sanitised, so use only with proper sanitisation!
*/
if ( operation.command === 'getAllData' ) {
resolve( this.db[ table ] );
} else if ( operation.command === 'getFilteredData' ) {
//
} else if ( operation.command === 'fullCustomCommand' ) {
//
} else if ( operation.command === 'addData' ) {
//
} else if ( operation.command === 'updateData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
//
}
} else if ( operation.command === 'deleteData' ) {
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
else {
//
}
} else if ( operation.command === 'InnerJoin' ) {
//
} else if ( operation.command === 'LeftJoin' ) {
//
} else if ( operation.command === 'RightJoin' ) {
//
} else if ( operation.command === 'checkDataAvailability' ) {
//
}
} );
}
}

View File

@@ -57,7 +57,7 @@ class SQLDB {
} );
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 ) => {
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, account_id INT ( 10 ) NOT NULL, seats VARCHAR( 60000 ), PRIMARY KEY ( order_id ), FOREIGN KEY ( account_id ) REFERENCES libreevent_users( account_id ) ) ENGINE=INNODB;', ( 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 ), 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;
this.sqlConnection.query( 'CREATE TABLE libreevent_admin ( account_id INT NOT NULL AUTO_INCREMENT, email TINYTEXT, pass TEXT, permissions VARCHAR( 1000 ), username TINYTEXT, two_fa TINYTEXT, PRIMARY KEY ( account_id ) );', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;

View File

@@ -43,6 +43,7 @@ class MailManager {
}
sendMailWithAttachment ( recipient, html, subject, attachments, from ) {
// Attachments have to be an array of objects that have filename and path as their keys
let text = html2text.convert( html, this.options );
let mailOptions = {
from: from,

View File

@@ -1,24 +0,0 @@
/*
* libreevent - successHandler.js
*
* Created by Janis Hutz 08/02/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
class PaymentHandler {
constructor () {
this.canceledTransactions = {};
}
async handleSuccess ( token ) {
console.log( token );
}
async handleError ( token ) {
}
}
module.exports = PaymentHandler;

View File

@@ -13,57 +13,63 @@ const db = require( '../../../db/db.js' );
const stripConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../../../config/payments.config.secret.json' ) ) )[ 'stripe' ];
const stripe = require( 'stripe' )( stripConfig[ 'APIKey' ] );
const bodyParser = require( 'body-parser' );
const ph = require( '../../../payments/paymentHandler.js' );
const paymentHandler = new ph();
const ticket = require( '../../../tickets/ticketGenerator.js' );
const TicketGenerator = new ticket();
const endpointSecret = stripConfig[ 'endpointSecret' ];
let sessionReference = {};
let waitingClients = {};
let paymentOk = {};
// TODO: Remove all selected tickets if timestamp more than user defined amount ago
module.exports = ( app, settings ) => {
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
let purchase = {
'line_items': [],
'mode': 'payment',
'success_url': settings.yourDomain + '/payments/success',
'cancel_url': settings.yourDomain + '/payments/canceled',
'submit_type': 'book',
'customer_email': req.body.mail
};
if ( req.session.loggedInUser ) {
let purchase = {
'line_items': [],
'mode': 'payment',
'success_url': settings.yourDomain + '/payments/success',
'cancel_url': settings.yourDomain + '/payments/canceled',
'submit_type': 'book',
'customer_email': req.session.username
};
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
purchase[ 'line_items' ].push( {
'price_data': {
'product_data': {
'name': data[ event ][ item ].name,
db.getDataSimple( 'temp', 'user_id', req.session.id ).then( dat => {
if ( dat[ 0 ] ) {
db.getJSONData( 'events' ).then( events => {
let data = JSON.parse( dat[ 0 ].data );
( async () => {
for ( let event in data ) {
for ( let item in data[ event ] ) {
purchase[ 'line_items' ].push( {
'price_data': {
'product_data': {
'name': data[ event ][ item ].name,
},
'currency': events[ event ].currency,
'unit_amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
},
'currency': events[ event ].currency,
'unit_amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
},
'quantity': data[ event ][ item ].count ?? 1,
} );
'quantity': data[ event ][ item ].count ?? 1,
} );
}
}
}
const session = await stripe.checkout.sessions.create( purchase );
sessionReference[ session.id ] = req.session.id;
res.send( session.url );
} )();
} );
} else {
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
}
} ).catch( error => {
console.error( '[ STRIPE ] DB ERROR: ' + error );
res.status( 500 ).send( 'ERR_DB' );
} );
const session = await stripe.checkout.sessions.create( purchase );
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
res.send( session.url );
} )();
} );
} else {
res.status( 400 ).send( 'ERR_UID_NOT_FOUND' );
}
} ).catch( error => {
console.error( '[ STRIPE ] DB ERROR: ' + error );
res.status( 500 ).send( 'ERR_DB' );
} );
} else {
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
}
} );
app.get( '/payments/status', ( request, response ) => {
@@ -75,7 +81,16 @@ module.exports = ( app, settings ) => {
response.status( 200 );
response.flushHeaders();
response.write( 'data: connected\n\n' );
// TODO: Finish up
waitingClients[ request.session.id ] = response;
} );
app.get( '/user/2fa/ping', ( request, response ) => {
if ( paymentOk[ request.session.token ] === 'ok' ) {
delete paymentOk[ request.session.token ];
response.send( { 'status': 'ok' } );
} else {
response.send( '' );
}
} );
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), ( req, res ) => {
@@ -92,7 +107,24 @@ module.exports = ( app, settings ) => {
}
if ( event.type === 'checkout.session.completed' ) {
paymentHandler.handleSuccess( sessionReference[ event.data.object.id ] );
setTimeout( () => {
waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
}, 2000 );
db.getDataSimple( 'temp', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( dat => {
db.getDataSimple( 'users', 'email', sessionReference[ event.data.object.id ][ 'email' ] ).then( user => {
if ( user[ 0 ] ) {
console.log( sessionReference[ event.data.object.id ][ 'tok' ] );
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 ] );
} );
} else {
console.log( sessionReference[ event.data.object.id ][ 'email' ] );
console.error( 'user not found' );
}
} );
} ).catch( err => {
console.error( err );
} );
}
res.status( 200 ).end();

View File

@@ -0,0 +1,3 @@
# Ticket Store
Here, all the tickets for all the orders are saved. You may delete the tickets here at any point. Should the user request their tickets once again, the tickets can simply be regenerated from the data stored in the database.

View File

@@ -0,0 +1,24 @@
/*
* libreevent - test.js
*
* Created by Janis Hutz 08/06/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
const express = require( 'express' );
let app = express();
const ticket = require( './ticketGenerator.js' );
const TicketGenerator = new ticket();
const http = require( 'http' );
app.get( '/', ( request, response ) => {
response.send( 'ok' );
TicketGenerator.generateTickets( { 'tok': 'hGids5PVsHm_KiK-Wd-8ekvwxpuUPrUX', 'email': 'info@janishutz.com' } );
} );
const PORT = process.env.PORT || 8080;
http.createServer( app ).listen( PORT );

View File

@@ -10,18 +10,34 @@
const pdfme = require( '@pdfme/generator' );
const db = require( '../db/db.js' );
const pdfLib = require( 'pdf-lib' );
const fs = require( 'fs' );
const path = require( 'path' );
const mm = require( '../mail/mailSender.js' );
const mailManager = new mm();
let createSSRApp = require( 'vue' ).createSSRApp;
let renderToString = require( 'vue/server-renderer' ).renderToString;
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
class TicketGenerator {
constructor () {
this.ticketQueue = {};
this.jobId = 0;
this.currentlyRunningJob = 0;
this.isRunning = false;
db.getJSONData( 'tickets' ).then( tickets => {
this.tickets = tickets;
} );
db.getJSONData( 'events' ).then( events => {
this.events = events;
} );
}
// TODO: Save to disk in case of crash of server / reboot / whatever
// and continue processing once back online
generateTicket ( event, data ) {
this.ticketQueue [ this.jobId ] = { 'event': event, 'data': data };
generateTickets ( order ) {
this.ticketQueue [ this.jobId ] = { 'order': order };
this.jobId += 1;
this.queueHandler();
}
@@ -30,28 +46,78 @@ class TicketGenerator {
queueHandler () {
if ( !this.isRunning ) {
this.isRunning = true;
this.ticketGenerator( this.ticketQueue[ this.jobId ][ 'event' ], this.ticketQueue[ this.jobId ][ 'data' ] ).then( pdf => {
console.log( pdf );
// TODO: Maybe write to disk
this.isRunning = false;
this.queueHandler();
} ).catch( error => {
console.error( '[ PDF GENERATOR ] ERROR: ' + error );
this.isRunning = false;
this.queueHandler();
// TODO: Add to FAILED db
} );
if ( this.ticketQueue[ this.currentlyRunningJob ] ) {
this.ticketGenerator( this.ticketQueue[ this.currentlyRunningJob ][ 'order' ] ).then( res => {
this.currentlyRunningJob += 1;
if ( res.status ) {
db.getDataSimple( 'users', 'account_id', res.user ).then( dat => {
if ( dat[ 0 ] ) {
( async () => {
const app = createSSRApp( {
data() {
return {
host: settings.yourDomain,
pageName: settings.name,
};
},
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/payments/ticketMail.html' ) )
} );
console.log( dat[ 0 ].email );
mailManager.sendMailWithAttachment( dat[ 0 ].email, await renderToString( app ), 'Thank you for your order', [
{
'filename': 'tickets.pdf',
'path': res.file,
}
], settings.mailSender
);
// db.writeDataSimple( 'orders', 'order_name', res.order, { 'processed': 'true' } );
} )();
}
} );
}
this.isRunning = false;
this.queueHandler();
} ).catch( error => {
console.error( '[ PDF GENERATOR ] ERROR: ' + error );
this.isRunning = false;
// TODO: Add to FAILED db
} );
}
}
}
ticketGenerator ( event, data ) {
ticketGenerator ( order ) {
return new Promise( ( resolve, reject ) => {
db.getJSONDataSimple( event ).then( template => {
pdfme.generate( { template, data } ).then( pdf => {
resolve( pdf );
} ).catch( error => {
reject( error );
} );
db.getDataSimple( 'orders', 'order_name', order.tok ).then( ord => {
if ( ord[ 0 ] ) {
( async () => {
let doc = await pdfLib.PDFDocument.create();
let pages = [];
const order = JSON.parse( ord[ 0 ].tickets );
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 ) );
}
}
const f = path.join( __dirname + '/store/' + ord[ 0 ].order_name + '.pdf' );
fs.writeFileSync( f, await doc.save() );
resolve( { 'status': true, 'file': f, 'user': ord[ 0 ].account_id, 'order': ord[ 0 ].order_name } );
} )();
} else {
reject( 'ERR_NO_ORDER' );
}
} ).catch( err => {
console.error( err );
} );
} );
}

View File

@@ -106,6 +106,8 @@ module.exports = ( app, settings ) => {
request.session.loggedInUser = true;
if ( responseObjects[ request.body.token ] ) {
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
responseObjects[ request.body.token ].end();
delete responseObjects[ request.body.token ];
} else {
authOk[ request.body.token ] = 'ok';
}
@@ -127,6 +129,7 @@ module.exports = ( app, settings ) => {
app.get( '/user/2fa/ping', ( request, response ) => {
if ( authOk[ request.session.token ] === 'ok' ) {
delete authOk[ request.session.token ];
response.send( { 'status': 'ok' } );
} else {
response.send( '' );