mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 13:24:24 +00:00
ticket generation working
This commit is contained in:
@@ -31,7 +31,7 @@ module.exports = ( app ) => {
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/API/:call', bodyParser.json(), ( req, res ) => {
|
||||
app.post( '/admin/API/:call', bodyParser.json( { limit: '20mb' } ), ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => {
|
||||
res.send( data );
|
||||
|
||||
@@ -54,6 +54,12 @@ class POSTHandler {
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'saveTickets' ) {
|
||||
db.writeJSONDataSimple( 'tickets', data.location, data.data ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
|
||||
@@ -92,7 +92,8 @@ if ( settings.init ) {
|
||||
|
||||
console.log( '[ Server ] loading plugins' );
|
||||
// TODO: load dynamically
|
||||
require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // setup routes
|
||||
require( './backend/plugins/payments/stripe/stripeRoutes.js' )( app, settings ); // stripe routes
|
||||
require( './backend/payments/paymentRoutes.js' )( app, settings ); // payment routes
|
||||
|
||||
app.use( ( request, response ) => {
|
||||
response.sendFile( file );
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -33,7 +33,6 @@ module.exports.getDataSimple = ( db, column, searchQuery ) => {
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
// resolve( '$2b$05$ElMYWoMjk7567lXkIkee.e.6cxCrWU4gkfuNLB8gmGYLQQPm7gT3O' );
|
||||
} );
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
class DataHelper {
|
||||
constructor () {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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' ) {
|
||||
//
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
|
||||
3
src/server/backend/tickets/store/README.md
Normal file
3
src/server/backend/tickets/store/README.md
Normal 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.
|
||||
24
src/server/backend/tickets/test.js
Normal file
24
src/server/backend/tickets/test.js
Normal 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 );
|
||||
@@ -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 );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -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( '' );
|
||||
|
||||
17
src/server/package-lock.json
generated
17
src/server/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"html-to-text": "^9.0.5",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"serve-static": "^1.15.0",
|
||||
"stripe": "^12.14.0",
|
||||
@@ -1991,6 +1992,22 @@
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/pdf-lib": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"pako": "^1.0.11",
|
||||
"tslib": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/peberminta": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"html-to-text": "^9.0.5",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"serve-static": "^1.15.0",
|
||||
"stripe": "^12.14.0",
|
||||
|
||||
@@ -4,11 +4,56 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payment Canceled</title>
|
||||
<style>
|
||||
body, html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: rgb(41, 40, 40);
|
||||
color: white;
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: 2%;
|
||||
background: linear-gradient(90deg, rgb(30, 36, 131), rgb(87, 66, 184), rgb(105, 115, 214), rgb(30, 36, 131), rgb(41, 128, 109), rgb(146, 50, 47));
|
||||
background-size: 300px;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 3s;
|
||||
font-size: 75%;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.submit:hover {
|
||||
background-size: 200%;
|
||||
background-position: -100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Payment Canceled</h1>
|
||||
<p>You have canceled your payment!</p>
|
||||
<p>This was a mistake? Head back to the payment page!</p>
|
||||
<button onclick="history.back()">Back to payment</button>
|
||||
<div class="content">
|
||||
<h1>Payment Canceled</h1>
|
||||
<p>You have canceled your payment!</p>
|
||||
<p>This was a mistake? Head back to the payment page!</p>
|
||||
<button onclick="history.back()" class="submit">Back to payment</button>
|
||||
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
69
src/server/ui/en/payments/ticketMail.html
Normal file
69
src/server/ui/en/payments/ticketMail.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Two-Factor Authentication</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 80%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ip {
|
||||
color: rgb(94, 94, 94);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
.verify {
|
||||
padding: 20px 30px;
|
||||
background-color: rgb(0, 7, 87);
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
transition: 0.5s all;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.verify:hover {
|
||||
background-color: rgb(0, 12, 139);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 999px) {
|
||||
.logo {
|
||||
width: 20vw;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 40vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<img :src="host + '/otherAssets/logo.png'" alt="Logo" class="logo">
|
||||
<h1>Thank you for your purchase at {{ pageName }}</h1>
|
||||
<p>Attached you may find your tickets. Enjoy the event!</p>
|
||||
<p>You may also find the tickets on your <a :href="host + '/account'">account page</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user