mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 05:14:23 +00:00
Restructuring for new way of installing libreevent
This commit is contained in:
3
src/server/.gitignore
vendored
3
src/server/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
# ignore node_modules folder for eslint
|
||||
node_modules
|
||||
webapp
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* libreevent - 2fa.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const token = require( '../backend/token.js' );
|
||||
let createSSRApp = require( 'vue' ).createSSRApp;
|
||||
let renderToString = require( 'vue/server-renderer' ).renderToString;
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
class TwoFA {
|
||||
constructor () {
|
||||
this.tokenStore = {};
|
||||
this.references = {};
|
||||
}
|
||||
|
||||
registerStandardAuthentication () {
|
||||
let tok = token.generateToken( 60 );
|
||||
while ( this.tokenStore[ tok ] ) {
|
||||
tok = token.generateToken( 60 );
|
||||
}
|
||||
this.tokenStore[ tok ] = { 'mode': 'standard' };
|
||||
return { 'token': tok };
|
||||
}
|
||||
|
||||
registerEnhancedAuthentication () {
|
||||
let tok = token.generateToken( 60 );
|
||||
while ( this.tokenStore[ tok ] ) {
|
||||
tok = token.generateToken( 60 );
|
||||
}
|
||||
let code = token.generateNumber( 6 );
|
||||
this.tokenStore[ tok ] = { 'mode': 'enhanced', 'code': code };
|
||||
return { 'code': code, 'token': tok };
|
||||
}
|
||||
|
||||
storeTokenReference ( token, sessionID ) {
|
||||
this.references[ token ] = sessionID;
|
||||
}
|
||||
|
||||
getTokenReference ( token ) {
|
||||
return this.references[ token ];
|
||||
}
|
||||
|
||||
verifyEnhanced ( token, number = '' ) {
|
||||
if ( this.tokenStore[ token ]?.mode === 'standard' ) return true;
|
||||
else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) {
|
||||
if ( this.tokenStore[ token ].code == number ) {
|
||||
delete this.tokenStore[ token ];
|
||||
return true;
|
||||
} else return false;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
verifySimple ( token ) {
|
||||
if ( this.tokenStore[ token ]?.mode === 'standard' ) {
|
||||
delete this.tokenStore[ token ];
|
||||
return 'standard';
|
||||
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
|
||||
else return 'invalid';
|
||||
}
|
||||
|
||||
async generateTwoFAMail ( token, ip, domain, pageName ) {
|
||||
const app = createSSRApp( {
|
||||
data() {
|
||||
return {
|
||||
token: token,
|
||||
ip: ip,
|
||||
host: domain,
|
||||
pageName: pageName,
|
||||
};
|
||||
},
|
||||
template: '' + fs.readFileSync( path.join( __dirname + '/twoFAMail.html' ) )
|
||||
} );
|
||||
|
||||
return await renderToString( app );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TwoFA;
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* libreevent - adminAPIRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const posth = require( './api/postHandler.js' );
|
||||
const geth = require( './api/getHandler.js' );
|
||||
const path = require( 'path' );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
const mlt = require( 'multer' );
|
||||
const pngToIco = require( 'png-to-ico' );
|
||||
const multer = mlt();
|
||||
const fs = require( 'fs' );
|
||||
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../config/settings.config.json' ) ) );
|
||||
const getHandler = new geth( settings );
|
||||
const postHandler = new posth( settings );
|
||||
|
||||
|
||||
// settings is missing in arguments which shouldn't pose any problem
|
||||
module.exports = ( app ) => {
|
||||
// Add specific routes here to have them be checked first to not get general handling
|
||||
|
||||
app.get( '/admin/getAPI/:call', ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
getHandler.handleCall( req.params.call, req.query ).then( data => {
|
||||
res.send( data );
|
||||
} ).catch( error => {
|
||||
res.status( error.code ?? 500 ).send( error.error );
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
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 );
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
res.status( error.code ?? 500 ).send( error.error );
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/events/uploadImages', multer.array( 'image', 2 ), ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
if ( req.query.event.includes( '/' ) || req.query.event.includes( '.' ) ) {
|
||||
res.status( 400 ).send( 'fp_wrong' );
|
||||
} else {
|
||||
for ( let file in req.files ) {
|
||||
if ( req.files[ file ].originalname === req.body.logo ) {
|
||||
fs.writeFileSync( path.join( __dirname + '/../assets/events/' + req.query.event + 'Logo.jpg' ), req.files[ file ].buffer );
|
||||
} else {
|
||||
fs.writeFileSync( path.join( __dirname + '/../assets/events/' + req.query.event + 'Banner.jpg' ), req.files[ file ].buffer );
|
||||
}
|
||||
}
|
||||
res.send( 'ok' );
|
||||
}
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/pages/uploadImages', multer.array( 'image', 1 ), ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
if ( req.query.image.includes( '/' ) || req.query.image.includes( '.' ) || req.query.template.includes( '/' ) || req.query.template.includes( '.' ) ) {
|
||||
res.status( 400 ).send( 'fp_wrong' );
|
||||
} else {
|
||||
fs.writeFileSync( path.join( __dirname + '/../ui/home/templates/' + req.query.template + '/assets/' + req.query.image + '.jpg' ), req.files[ 0 ].buffer );
|
||||
res.send( 'ok' );
|
||||
}
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/logo/upload', multer.array( 'image', 1 ), ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
fs.writeFileSync( path.join( __dirname + '/../assets/logo.png' ), req.files[ 0 ].buffer );
|
||||
|
||||
pngToIco( path.join( __dirname + '/../assets/logo.png' ) ).then( buf => {
|
||||
fs.writeFileSync( path.join( __dirname + '/../webapp/main/dist/favicon.ico' ), buf );
|
||||
} ).catch( () => {
|
||||
console.error( '[ ICON CONVERTER ] Failed to convert png to ico file' );
|
||||
} );
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* libreevent - routes.js (admin)
|
||||
*
|
||||
* Created by Janis Hutz 03/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
// const db = require( './db/db.js' );
|
||||
const pwdmanager = require( './pwdmanager.js' );
|
||||
const db = require( '../backend/db/db.js' );
|
||||
const auth = require( './2fa.js' );
|
||||
const twoFA = new auth();
|
||||
const path = require( 'path' );
|
||||
const mail = require( '../backend/mail/mailSender.js' );
|
||||
const mailManager = new mail();
|
||||
const bodyParser = require( 'body-parser' );
|
||||
|
||||
let responseObjects = {};
|
||||
let authOk = {};
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
/*
|
||||
Admin login route that checks the password
|
||||
*/
|
||||
|
||||
app.post( '/admin/auth', 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' ];
|
||||
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
|
||||
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
|
||||
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( tok, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
|
||||
request.session.token = tok;
|
||||
response.send( { 'status': '2fa' } );
|
||||
} )();
|
||||
} else if ( data.twoFA === 'enhanced' ) {
|
||||
( async () => {
|
||||
let res = twoFA.registerEnhancedAuthentication();
|
||||
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
|
||||
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
|
||||
if ( request.body.mail === 'root' ) {
|
||||
db.getJSONDataSimple( 'rootAccount', 'email' ).then( email => {
|
||||
( async () => {
|
||||
mailManager.sendMail( email, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
|
||||
} )();
|
||||
} );
|
||||
} else {
|
||||
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify admin account login', settings.mailSender );
|
||||
}
|
||||
request.session.token = res.token;
|
||||
response.send( { 'status': '2fa+', 'code': res.code } );
|
||||
} )();
|
||||
} else {
|
||||
request.session.loggedInAdmin = true;
|
||||
response.send( { 'status': 'ok' } );
|
||||
}
|
||||
} else {
|
||||
response.send( { 'status': 'pwErr' } );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
response.send( 'missingCredentials' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/admin/2fa', ( request, response ) => {
|
||||
let tokType = twoFA.verifySimple( request.query.token );
|
||||
if ( tokType === 'standard' ) {
|
||||
request.session.loggedInAdmin = true;
|
||||
responseObjects[ request.query.token ].write( 'data: authenticated\n\n' );
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faSimple.html' ) );
|
||||
} else if ( tokType === 'enhanced' ) {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faEnhancedAdmin.html' ) );
|
||||
} else {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faInvalid.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/2fa/verify', bodyParser.json(), ( request, response ) => {
|
||||
let verified = twoFA.verifyEnhanced( request.body.token, request.body.code );
|
||||
if ( verified ) {
|
||||
request.session.loggedInAdmin = true;
|
||||
responseObjects[ request.body.token ].write( 'data: authenticated\n\n' );
|
||||
response.send( 'ok' );
|
||||
} else response.send( 'wrong' );
|
||||
} );
|
||||
|
||||
app.get( '/admin/2fa/check', ( request, response ) => {
|
||||
response.writeHead( 200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
} );
|
||||
response.status( 200 );
|
||||
response.flushHeaders();
|
||||
response.write( 'data: connected\n\n' );
|
||||
responseObjects[ request.session.token ] = response;
|
||||
} );
|
||||
|
||||
app.get( '/admin/2fa/ping', ( request, response ) => {
|
||||
if ( authOk[ request.session.token ] === 'ok' ) {
|
||||
response.send( { 'status': 'ok' } );
|
||||
} else {
|
||||
response.send( '' );
|
||||
}
|
||||
} );
|
||||
|
||||
// app.get( '/test/login', ( request, response ) => {
|
||||
// request.session.loggedInAdmin = true;
|
||||
// response.send( 'Logged in' );
|
||||
// } );
|
||||
|
||||
app.get( '/admin/logout', ( request, response ) => {
|
||||
request.session.loggedInAdmin = false;
|
||||
response.send( 'logged out' );
|
||||
} );
|
||||
|
||||
app.get( '/api/getAuth', ( request, response ) => {
|
||||
response.send( { 'admin': request.session.loggedInAdmin ? true : false, 'user': request.session.loggedInUser ? true : false } );
|
||||
} );
|
||||
};
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* libreevent - getHandler.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( '../../backend/db/db.js' );
|
||||
const pm = require( '../../backend/plugins/manager.js' );
|
||||
const spm = require( '../startPageManager.js' );
|
||||
|
||||
class GETHandler {
|
||||
constructor ( settings ) {
|
||||
this.pluginManager = new pm( settings );
|
||||
this.settings = settings;
|
||||
this.startPageManager = new spm( settings );
|
||||
}
|
||||
|
||||
handleCall ( call, query ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
if ( call === 'getSeatplan' ) {
|
||||
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
|
||||
if ( Object.keys( data ).length > 0 ) {
|
||||
resolve( data[ 'save' ] );
|
||||
} else {
|
||||
reject( { 'code': 400, 'error': 'No data found for this location' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getSeatplanDraft' ) {
|
||||
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
|
||||
if ( Object.keys( data ).length > 0 ) {
|
||||
if ( Object.keys( data[ 'draft' ] ).length > 0 ) {
|
||||
resolve( data[ 'draft' ] );
|
||||
} else {
|
||||
resolve( data[ 'save' ] );
|
||||
}
|
||||
} else {
|
||||
reject( { 'code': 400, 'error': 'No data found for this location' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} else if ( call === 'getLocations' ) {
|
||||
db.getJSONData( 'locations' ).then( 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 } );
|
||||
} );
|
||||
} else if ( call === 'getEvent' ) {
|
||||
db.getJSONDataSimple( 'eventDrafts', query.event ).then( data => {
|
||||
if ( Object.keys( data ).length > 1 ) {
|
||||
resolve( data );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'EventNotFound' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getEventStatus' ) {
|
||||
db.getJSONDataSimple( 'events', query.event ).then( data => {
|
||||
if ( Object.keys( data ) ) {
|
||||
resolve( true );
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getAllEvents' ) {
|
||||
db.getJSONData( 'eventDrafts' ).then( data => {
|
||||
db.getJSONData( 'events' ).then( dat => {
|
||||
resolve( { 'live': dat ?? {}, 'drafts': data ?? {} } );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getCurrency' ) {
|
||||
resolve( this.settings.currency );
|
||||
} else if ( call === 'getAdminAccounts' ) {
|
||||
db.getData( 'admin' ).then( data => {
|
||||
if ( data[ 0 ] ) {
|
||||
resolve( { 'data': data, 'status': 'ok' } );
|
||||
} else {
|
||||
resolve( { 'data': {}, 'status': 'empty' } );
|
||||
}
|
||||
} ).catch( err => {
|
||||
reject( { 'code': 500, 'message': 'ERR_DB: ' + err } );
|
||||
} );
|
||||
} else if ( call === 'getRootAccountDetails' ) {
|
||||
db.getJSONData( 'rootAccount' ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( err => {
|
||||
reject( { 'code': 500, 'message': 'ERR_DB: ' + err } );
|
||||
} );
|
||||
} else if ( call === 'getPaymentGatewaySettings' ) {
|
||||
this.pluginManager.loadPaymentGatewaySettings().then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( err => {
|
||||
reject( { 'code': 500, 'error': err } );
|
||||
} );
|
||||
} else if ( call === 'getSettings' ) {
|
||||
resolve( this.settings );
|
||||
} else if ( call === 'getAllPlugins' ) {
|
||||
resolve( this.pluginManager.getPlugins() );
|
||||
} else if ( call === 'getStartPageSettings' ) {
|
||||
resolve( this.startPageManager.loadStartPagePreferences( query.name ) );
|
||||
} else if ( call === 'getAllStartPages' ) {
|
||||
resolve( this.startPageManager.findAllStartPageTemplates() );
|
||||
} else if ( call === 'buildStartPage' ) {
|
||||
( async() => {
|
||||
if ( await this.startPageManager.renderStartPage( query.page ) ) {
|
||||
resolve( 'ok' );
|
||||
} else {
|
||||
reject( { 'code': 412, 'error': 'Missing entries' } );
|
||||
}
|
||||
} )();
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GETHandler;
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* libreevent - postHandler.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( '../../backend/db/db.js' );
|
||||
const pwdmanager = require( '../pwdmanager.js' );
|
||||
const pm = require( '../../backend/plugins/manager.js' );
|
||||
const spm = require( '../startPageManager.js' );
|
||||
const startPageManager = new spm();
|
||||
|
||||
|
||||
|
||||
class POSTHandler {
|
||||
constructor ( settings ) {
|
||||
this.pluginManager = new pm( settings );
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
handleCall ( call, data, lang ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
console.log( lang );
|
||||
if ( call === 'saveSeatplanDraft' ) {
|
||||
db.getJSONDataSimple( 'seatplan', data.location ).then( res => {
|
||||
let dat = res;
|
||||
dat[ 'draft' ] = data.data;
|
||||
db.writeJSONDataSimple( 'seatplan', data.location, dat ).then( resp => {
|
||||
db.getJSONDataSimple( 'locations', data.location ).then( dat => {
|
||||
let s = dat;
|
||||
s[ 'totalSeats' ] = data.data.seatInfo.count;
|
||||
db.writeJSONDataSimple( 'locations', data.location, s ).then( () => {
|
||||
resolve( resp );
|
||||
} );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} );
|
||||
} else if ( call === 'saveSeatplan' ) {
|
||||
db.writeJSONDataSimple( 'seatplan', data.location, { 'draft': {}, 'save': data.data } ).then( resp => {
|
||||
db.getJSONDataSimple( 'locations', data.location ).then( dat => {
|
||||
let s = dat;
|
||||
s[ 'totalSeats' ] = data.data.seatInfo.count;
|
||||
db.writeJSONDataSimple( 'locations', data.location, s ).then( () => {
|
||||
resolve( resp );
|
||||
} );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'saveLocations' ) {
|
||||
db.getJSONData( 'seatplan' ).then( res => {
|
||||
let dat = res;
|
||||
for ( let loc in data.updated ) {
|
||||
if ( res[ loc ] ) {
|
||||
dat[ data.updated[ loc ] ] = res[ loc ];
|
||||
delete dat[ loc ];
|
||||
}
|
||||
}
|
||||
db.writeJSONData( 'seatplan', dat ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
|
||||
db.writeJSONData( 'locations', data.data ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deleteLocation' ) {
|
||||
db.deleteJSONDataSimple( 'locations', data.location ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'createEvent' ) {
|
||||
db.getJSONDataSimple( 'eventDrafts', data.event ).then( dat => {
|
||||
if ( Object.keys( dat ).length < 1 ) {
|
||||
db.writeJSONDataSimple( 'eventDrafts', data.event, { 'name': 'Unnamed event', 'description': '', 'location': '', 'date': '', 'categories': {}, 'ageGroups': { '1': { 'id': 1, 'name': 'Child', 'age': '0 - 15.99' }, '2': { 'id': 2, 'name': 'Adult' } }, 'maxTickets': 2, 'eventID': data.event } ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 409, 'error': 'ExistsAlready' } );
|
||||
}
|
||||
} );
|
||||
} else if ( call === 'saveEvent' ) {
|
||||
db.writeJSONDataSimple( 'eventDrafts', data.event, data.eventData ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deployEvent' ) {
|
||||
db.writeJSONDataSimple( 'events', data.event, data.eventData ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deleteEvent' ) {
|
||||
db.deleteJSONDataSimple( 'eventDrafts', data.event ).then( () => {
|
||||
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'undeployEvent' ) {
|
||||
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).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 if ( call === 'createAdminAccount' ) {
|
||||
let dat = data;
|
||||
pwdmanager.hashPassword( dat.pass ).then( hash => {
|
||||
dat[ 'pass' ] = hash;
|
||||
db.writeDataSimple( 'admin', 'email', data.email, dat ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} );
|
||||
} else if ( call === 'updateAdminAccount' ) {
|
||||
if ( data.pass ) {
|
||||
let dat = data;
|
||||
pwdmanager.hashPassword( data.pass ).then( hash => {
|
||||
dat[ 'pass' ] = hash;
|
||||
db.writeDataSimple( 'admin', 'email', data.email, dat ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
db.writeDataSimple( 'admin', 'email', data.email, data ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
}
|
||||
} else if ( call === 'deleteAdminAccount' ) {
|
||||
db.deleteDataSimple( 'admin', 'email', data.email ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'updateSettings' ) {
|
||||
this.settings[ 'twoFA' ] = data.twoFA;
|
||||
this.settings[ 'currency' ] = data.currency;
|
||||
this.settings[ 'payments' ] = data.payments;
|
||||
this.settings[ 'ticketTimeout' ] = data.ticketTimeout;
|
||||
db.saveSettings( this.settings );
|
||||
db.getJSONData( 'events' ).then( dat => {
|
||||
let updated = dat;
|
||||
for ( let event in updated ) {
|
||||
updated[ event ][ 'currency' ] = data.currency;
|
||||
}
|
||||
db.writeJSONData( 'events', updated );
|
||||
} );
|
||||
resolve( 'ok' );
|
||||
} else if ( call === 'updatePaymentGatewaySettings' ) {
|
||||
this.pluginManager.savePaymentGatewaySettings( data ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( err => {
|
||||
reject( { 'code': 500, 'message': err } );
|
||||
} );
|
||||
} else if ( call === 'savePageSettings' ) {
|
||||
startPageManager.saveStartPagePreferences( data.page, data.preferences );
|
||||
resolve( 'ok' );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = POSTHandler;
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* libreevent - appApiRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 08/19/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const bodyParser = require( 'body-parser' );
|
||||
const db = require( '../backend/db/db.js' );
|
||||
const pwHandler = require( './pwdmanager.js' );
|
||||
|
||||
module.exports = ( app ) => {
|
||||
console.log( '[ APP API ] Loaded!' );
|
||||
app.post( '/app/authenticate', bodyParser.json(), ( req, res ) => {
|
||||
pwHandler.checkpassword( req.body.email, req.body.password ).then( status => {
|
||||
if ( status ) {
|
||||
if ( status.status ) {
|
||||
res.send( 'authOk' );
|
||||
} else {
|
||||
res.send( 'wrong' );
|
||||
}
|
||||
} else {
|
||||
res.send( 'wrong' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
app.post( '/app/ticketLookup', bodyParser.json(), ( req, res ) => {
|
||||
pwHandler.checkpassword( req.body.email, req.body.password ).then( status => {
|
||||
if ( status ) {
|
||||
if ( status.status ) {
|
||||
// extract order name
|
||||
let indexOfOrderNameEnd = req.body.ticketID.lastIndexOf( '_' );
|
||||
if ( indexOfOrderNameEnd > req.body.ticketID.length - 5 ) {
|
||||
indexOfOrderNameEnd = req.body.ticketID.slice( 0, req.body.ticketID.length - 5 ).lastIndexOf( '_' );
|
||||
}
|
||||
db.getDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, indexOfOrderNameEnd ) ).then( dat => {
|
||||
if ( dat[ 0 ] ) {
|
||||
let tickets = JSON.parse( dat[ 0 ][ 'tickets' ] );
|
||||
const event = req.body.ticketID.slice( indexOfOrderNameEnd + 1, req.body.ticketID.lastIndexOf( '-' ) );
|
||||
const ticket = req.body.ticketID.slice( req.body.ticketID.lastIndexOf( '-' ) + 1, req.body.ticketID.length );
|
||||
if ( tickets[ event ] ) {
|
||||
if ( tickets[ event ][ ticket ] ) {
|
||||
if ( tickets[ event ][ ticket ][ 'count' ] ) {
|
||||
if ( !tickets[ event ][ ticket ][ 'used' ] ) {
|
||||
tickets[ event ][ ticket ][ 'used' ] = 0;
|
||||
}
|
||||
if ( tickets[ event ][ ticket ][ 'used' ] == tickets[ event ][ ticket ][ 'count' ] ) {
|
||||
tickets[ event ][ ticket ][ 'used' ] += 1;
|
||||
db.writeDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, req.body.ticketID.lastIndexOf( '_' ) ), { 'tickets': JSON.stringify( tickets ) } );
|
||||
res.send( 'ticketValid' );
|
||||
} else {
|
||||
res.send( 'ticketInvalid' );
|
||||
}
|
||||
} else {
|
||||
if ( !tickets[ event ][ ticket ][ 'invalidated' ] ) {
|
||||
tickets[ event ][ ticket ][ 'invalidated' ] = true;
|
||||
db.writeDataSimple( 'orders', 'order_name', req.body.ticketID.slice( 0, req.body.ticketID.lastIndexOf( '_' ) ), { 'tickets': JSON.stringify( tickets ) } );
|
||||
res.send( 'ticketValid' );
|
||||
} else {
|
||||
res.send( 'ticketInvalid' );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.send( 'ticketInvalid' );
|
||||
}
|
||||
} else {
|
||||
res.send( 'ticketInvalid' );
|
||||
}
|
||||
} else {
|
||||
res.send( 'ticketInvalid' );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
res.send( 'wrong' );
|
||||
}
|
||||
} else {
|
||||
res.send( 'wrong' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* libreevent - pwdmanager.js
|
||||
*
|
||||
* Created by Janis Hutz 03/26/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
These functions are required to verify user login and to create new users
|
||||
and to hash new passwords (if user changes password.) This here is only
|
||||
used for the admin panel, another one is used for the normal user accounts
|
||||
to separate the two for additional security.
|
||||
*/
|
||||
|
||||
// import and init
|
||||
const bcrypt = require( 'bcrypt' );
|
||||
const db = require( '../backend/db/db.js' );
|
||||
|
||||
module.exports.checkpassword = ( username, password ) => {
|
||||
return new Promise( resolve => {
|
||||
if ( username === 'root' ) {
|
||||
db.getJSONData( 'rootAccount' ).then( account => {
|
||||
bcrypt.compare( password, account.pass ).then( res => {
|
||||
resolve( { 'status': res, 'twoFA': 'enhanced' } );
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
db.getDataSimple( 'admin', 'email', username ).then( data => {
|
||||
if ( data ) {
|
||||
if ( data[ 0 ] ) {
|
||||
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
|
||||
resolve( { 'status': res, 'twoFA': data[ 0 ].two_fa } );
|
||||
} );
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.hashPassword = ( password ) => {
|
||||
return new Promise( resolve => {
|
||||
resolve( bcrypt.hashSync( password, 10 ) );
|
||||
} );
|
||||
};
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* libreevent - startPageManager.js
|
||||
*
|
||||
* Created by Janis Hutz 09/04/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
let createSSRApp = require( 'vue' ).createSSRApp;
|
||||
let renderToString = require( 'vue/server-renderer' ).renderToString;
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const db = require( '../backend/db/db.js' );
|
||||
|
||||
class StartPageManager {
|
||||
constructor ( settings ) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
saveStartPagePreferences( startPageName, preferences ) {
|
||||
let conf = {};
|
||||
for ( let setting in preferences ) {
|
||||
conf[ setting ] = preferences[ setting ][ 'value' ];
|
||||
}
|
||||
fs.writeFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.config.json' ), JSON.stringify( conf ) );
|
||||
}
|
||||
|
||||
loadStartPagePreferences( startPageName ) {
|
||||
let conf, options;
|
||||
try {
|
||||
options = JSON.parse( fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.json' ) ) );
|
||||
conf = JSON.parse( fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/startPage.config.json' ) ) );
|
||||
return { 'conf': conf, 'options': options };
|
||||
} catch ( err ) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
findAllStartPageTemplates() {
|
||||
return fs.readdirSync( path.join( __dirname + '/../ui/home/templates/' ) );
|
||||
}
|
||||
|
||||
setActiveStartPage( startPageName ) {
|
||||
this.settings[ 'startPage' ] = startPageName;
|
||||
db.saveSettings( this.settings );
|
||||
}
|
||||
|
||||
async renderStartPage( startPageName ) {
|
||||
this.setActiveStartPage( startPageName );
|
||||
let self = this;
|
||||
const app = createSSRApp( {
|
||||
data() {
|
||||
return {
|
||||
'data': self.loadStartPagePreferences( startPageName ),
|
||||
'pageName': self.settings.pageName,
|
||||
};
|
||||
},
|
||||
template: '' + fs.readFileSync( path.join( __dirname + '/../ui/home/templates/' + startPageName + '/index.html' ) )
|
||||
} );
|
||||
|
||||
fs.writeFileSync( path.join( __dirname + '/../ui/home/active/en/index.html' ), await renderToString( app ) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StartPageManager;
|
||||
@@ -1,70 +0,0 @@
|
||||
<!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>Welcome back!</h1>
|
||||
<p>It looks like someone is trying to sign in to your admin account at {{ pageName }}. If it was you, please click the button below to confirm the login. If not, please <a :href="host + '/admin/settings'">change</a> your password immediately or have it changed by the root account!</p>
|
||||
<p class="ip">Logging in from IP {{ ip }}.</p>
|
||||
<a :href="host + '/admin/2fa?token=' + token" class="verify">Verify</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* libreevent - app.js
|
||||
*
|
||||
* Created by Janis Hutz 02/26/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const express = require( 'express' );
|
||||
let app = express();
|
||||
const path = require( 'path' );
|
||||
const expressSession = require( 'express-session' );
|
||||
const cookieParser = require( 'cookie-parser' );
|
||||
const http = require( 'http' );
|
||||
const fs = require( 'fs' );
|
||||
const token = require( './backend/token.js' );
|
||||
|
||||
console.log( `
|
||||
|
||||
_ _ _ _
|
||||
| (_) | | |
|
||||
| |_| |__ _ __ ___ _____ _____ _ __ | |_
|
||||
| | | '_ \\| '__/ _ \\/ _ \\ \\ / / _ \\ '_ \\| __|
|
||||
| | | |_) | | | __/ __/\\ V / __/ | | | |_
|
||||
|_|_|_.__/|_| \\___|\\___| \\_/ \\___|_| |_|\\__|
|
||||
|
||||
|
||||
|
||||
|
||||
-------------------------------
|
||||
|
||||
==> Welcome to libreevent!
|
||||
|
||||
|
||||
==> You are running Version V1.0.0
|
||||
|
||||
Below you can see all important things that happen during operation.
|
||||
libreevent logs all errors in the console such that they appear in the
|
||||
log files when running it with an output pipe (which you should definitely do)
|
||||
|
||||
To do this run the following command when starting libreevent:
|
||||
'node app.js > libreevent_log.txt'
|
||||
|
||||
` );
|
||||
|
||||
console.log( '[ Server ] loading settings' );
|
||||
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/config/settings.config.json' ) ) );
|
||||
|
||||
|
||||
// Route for static html file for start page (page is compiled using
|
||||
// Vue SSR and gets its support files (e.g. CSS and JS files) from
|
||||
// the /home/supportFiles/:file route plus its assets from the /otherAssets/:file
|
||||
// route).
|
||||
if ( settings.setupDone ) {
|
||||
app.get( '/', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/ui/home/active/en/index.html' ) );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
// Set up static routes for static file serving (performance wise not
|
||||
// that good, but way easier to set up)
|
||||
console.log( '[ Server ] Setting up static routes' );
|
||||
if ( settings.setupDone ) {
|
||||
app.use( express.static( 'webapp/main/dist' ) );
|
||||
} else {
|
||||
app.use( express.static( 'webapp/setup/dist' ) );
|
||||
}
|
||||
|
||||
// initialise express with middlewares
|
||||
console.log( '[ Server ] loading and initializing middlewares' );
|
||||
app.use( expressSession( {
|
||||
secret: token.generateToken( 60 ),
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
sameSite: 'none',
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
}
|
||||
} ) );
|
||||
|
||||
app.use( cookieParser() );
|
||||
|
||||
let file = path.join( __dirname + '/webapp/main/dist/index.html' );
|
||||
|
||||
if ( settings.setupDone ) {
|
||||
console.log( '[ Server ] loading backend components' );
|
||||
require( './backend/helperRoutes.js' )( app, settings ); // Helper routes
|
||||
require( './admin/adminRoutes.js' )( app, settings ); // admin routes
|
||||
require( './admin/adminAPIRoutes.js' )( app, settings ); // admin api routes
|
||||
require( './admin/appApiRoutes.js' )( app, settings ); // app api routes
|
||||
require( './backend/userAPIRoutes.js' )( app, settings ); // user api routes
|
||||
require( './backend/userRoutes.js' )( app, settings ); // user routes
|
||||
require( './backend/payments/paymentRoutes.js' )( app, settings ); // payment routes
|
||||
require( './backend/plugins/pluginLoader.js' )( app, settings ); // plugin loader
|
||||
} else {
|
||||
console.log( '[ Setup ] Loading setup routes' );
|
||||
require( './setup/setupRoutes.js' )( app, settings ); // setup routes
|
||||
file = path.join( __dirname + '/webapp/setup/dist/index.html' );
|
||||
}
|
||||
|
||||
// handling of any unknown route. Returns the SPA index.html file which
|
||||
// initiates loading of the SPA
|
||||
app.use( ( request, response ) => {
|
||||
response.sendFile( file );
|
||||
} );
|
||||
|
||||
console.log( '\n\n[ Server ] loading complete!\n\n' );
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
console.log( '[ Server ] listening on port ' + PORT );
|
||||
http.createServer( app ).listen( PORT );
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 57 KiB |
1
src/server/assets/events/.gitignore
vendored
1
src/server/assets/events/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.jpg
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 698 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 698 KiB |
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* libreevent - getHandler.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( '../db/db.js' );
|
||||
|
||||
class GETHandler {
|
||||
constructor () {
|
||||
|
||||
}
|
||||
|
||||
handleCall ( call, query, session, settings ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
if ( call === 'getSeatplan' ) {
|
||||
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
|
||||
if ( Object.keys( data ).length > 0 ) {
|
||||
resolve( data[ 'save' ] );
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'No data found for this location' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'message': error } );
|
||||
} );
|
||||
} else if ( call === 'getReservedSeats' ) {
|
||||
if ( query.event ) {
|
||||
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
|
||||
resolve( { 'user': dat[ 0 ] ? JSON.parse( dat[ 0 ].data )[ query.event ] ?? {} : {} } );
|
||||
} ).catch( () => {
|
||||
reject( { 'code': 500, 'message': 'ERR_DB' } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 400, 'message': 'Bad request, missing event query' } );
|
||||
}
|
||||
} else if ( call === 'getEvent' ) {
|
||||
db.getJSONDataSimple( 'events', query.event ).then( data => {
|
||||
if ( Object.keys( data ) ) {
|
||||
resolve( data );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'EventNotFound' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getAllEvents' ) {
|
||||
db.getJSONData( 'events' ).then( data => {
|
||||
resolve( data ?? {} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'extendTicketDeletion' ) {
|
||||
db.writeDataSimple( 'temp', 'user_id', session.id, { 'timestamp': new Date().toString() } );
|
||||
resolve( 'extended' );
|
||||
} else if ( call === 'getName' ) {
|
||||
resolve( { 'name': settings.name } );
|
||||
} else if ( call === 'reloadData' ) {
|
||||
resolve( 'ok' );
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GETHandler;
|
||||
@@ -1,334 +0,0 @@
|
||||
/*
|
||||
* libreevent - postHandler.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require( 'path' );
|
||||
const db = require( '../db/db.js' );
|
||||
const fs = require( 'fs' );
|
||||
const pwHandler = require( '../credentials/pwdmanager.js' );
|
||||
|
||||
class POSTHandler {
|
||||
constructor () {
|
||||
this.loadData();
|
||||
|
||||
this.temporarilySelected = {};
|
||||
this.temporarilySelectedTotals = {};
|
||||
this.temporaryTotals = {};
|
||||
this.freeSeats = {};
|
||||
this.settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
|
||||
|
||||
/*
|
||||
Here, GC-Duty is scheduled to run every so often (defined in settings.config.json file, no GUI setting available.
|
||||
If you are a developer and are thinking about adding a GUI setting for this, please consider that this is a
|
||||
advanced setting and many people might not understand what it changes. The config file is mentioned in the
|
||||
"advanced settings" section of the admin panel documentation where all the available settings in the config file
|
||||
are explained in some detail.)
|
||||
*/
|
||||
setInterval( () => {
|
||||
this.gc();
|
||||
}, parseInt( this.settings.gcInterval ) * 1000 );
|
||||
}
|
||||
|
||||
loadData () {
|
||||
db.getJSONData( 'booked' ).then( dat => {
|
||||
this.allSelectedSeats = dat;
|
||||
db.getJSONData( 'events' ).then( dat => {
|
||||
this.events = dat;
|
||||
this.ticketTotals = {};
|
||||
for ( let event in this.events ) {
|
||||
this.ticketTotals[ event ] = this.events[ event ][ 'totalSeats' ];
|
||||
}
|
||||
|
||||
for ( let event in this.allSelectedSeats ) {
|
||||
for ( let t in this.allSelectedSeats[ event ] ) {
|
||||
if ( this.allSelectedSeats[ event ][ t ][ 'count' ] ) {
|
||||
this.ticketTotals[ event ] -= parseInt( this.allSelectedSeats[ event ][ t ][ 'count' ] );
|
||||
} else {
|
||||
this.ticketTotals[ event ] -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.countFreeSeats();
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
gc() {
|
||||
// this function acts as the database garbage collector. TicketTimeout can be changed from the GUI.
|
||||
db.getData( 'temp' ).then( tempData => {
|
||||
let data = tempData;
|
||||
console.info( '[ DB ] Garbage Collecting' );
|
||||
for ( let item in data ) {
|
||||
if ( new Date( data[ item ][ 'timestamp' ] ).getTime() + ( parseInt( this.settings.ticketTimeout ) * 1000 ) <= new Date().getTime() ) {
|
||||
let dat = JSON.parse( data[ item ].data );
|
||||
for ( let event in dat ) {
|
||||
for ( let ticket in dat[ event ] ) {
|
||||
this.temporaryTotals -= this.temporarilySelectedTotals[ data[ item ].user_id ][ event ][ ticket ];
|
||||
delete this.temporarilySelectedTotals[ data[ item ].user_id ][ event ][ ticket ];
|
||||
delete this.temporarilySelected[ event ][ ticket ];
|
||||
}
|
||||
}
|
||||
db.deleteDataSimple( 'temp', 'entry_id', data[ item ].entry_id ).then( () => {
|
||||
console.debug( '[ DB ] Garbage collected a session' );
|
||||
} ).catch( err => {
|
||||
console.error( '[ DB ] GC-ERROR: ' + err );
|
||||
} );
|
||||
}
|
||||
}
|
||||
this.countFreeSeats();
|
||||
} );
|
||||
}
|
||||
|
||||
// Add lang in the future
|
||||
handleCall ( call, data, session ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
if ( call === 'reserveTicket' ) {
|
||||
if ( data.count || data.count === 0 ) {
|
||||
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
|
||||
// const id = data.id.slice( 0, data.id.indexOf( '_' ) );
|
||||
if ( dat[ 0 ] ) {
|
||||
// data.count is the total amount of tickets currently selected
|
||||
let totalTickets = 0;
|
||||
|
||||
// sum up total of tickets
|
||||
let info = JSON.parse( dat[ 0 ].data );
|
||||
let tickets = info[ data.eventID ] ?? {};
|
||||
for ( let ticket in tickets ) {
|
||||
if ( tickets[ ticket ].count ) {
|
||||
totalTickets += tickets[ ticket ].count;
|
||||
} else {
|
||||
totalTickets += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// check if total ticket count exceeds max tickets per order
|
||||
if ( this.settings.maxTickets !== 0 ) {
|
||||
if ( totalTickets >= this.settings.maxTickets ) {
|
||||
reject( { 'code': 418, 'message': 'ERR_TOO_MANY_TICKETS' } );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !this.temporarilySelectedTotals[ session.id ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 0;
|
||||
}
|
||||
|
||||
let ticketCount = data.count;
|
||||
// check if total ticket count exceeds max tickets for this event and adjust if necessary
|
||||
if ( this.events[ data.eventID ].maxTickets == 0 || totalTickets < this.events[ data.eventID ].maxTickets ) {
|
||||
// check if enough tickets are still available
|
||||
let isExceeded = false;
|
||||
if ( ( totalTickets - ( tickets[ data.id ] ? tickets[ data.id ][ 'count' ] : 0 ) + ticketCount ) > this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + totalTickets && data.count > 0 ) {
|
||||
ticketCount = this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + ( tickets[ data.id ] ? tickets[ data.id ][ 'count' ] : 0 );
|
||||
isExceeded = true;
|
||||
}
|
||||
info[ data.eventID ] = tickets;
|
||||
if ( data.count < 1 ) {
|
||||
if ( Object.keys( info[ data.eventID ] ).length < 1 ) {
|
||||
delete info[ data.eventID ];
|
||||
} else {
|
||||
delete info[ data.eventID ][ data.id ];
|
||||
}
|
||||
} else {
|
||||
info[ data.eventID ][ data.id ] = data;
|
||||
}
|
||||
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
|
||||
if ( !this.temporarilySelected[ data.eventID ] ) {
|
||||
this.temporarilySelected[ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporaryTotals[ data.eventID ] ) {
|
||||
this.temporaryTotals[ data.eventID ] = 0;
|
||||
}
|
||||
this.temporarilySelected[ data.eventID ][ data.id ] = info[ data.eventID ] ? info[ data.eventID ][ data.id ] : undefined;
|
||||
this.temporaryTotals[ data.eventID ] -= this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
|
||||
this.temporaryTotals[ data.eventID ] += ticketCount;
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = ticketCount;
|
||||
this.countFreeSeats();
|
||||
if ( isExceeded ) {
|
||||
reject( { 'code': 409, 'message': { 'count': ticketCount } } );
|
||||
} else {
|
||||
resolve( { 'status': 'ok' } );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 418, 'message': { 'count': this.settings.maxTickets } } );
|
||||
}
|
||||
} else {
|
||||
if ( !this.temporarilySelectedTotals[ session.id ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 0;
|
||||
}
|
||||
|
||||
let ticketCount = data.count;
|
||||
// check if total ticket count exceeds max tickets for this event and adjust if necessary
|
||||
if ( this.events[ data.eventID ].maxTickets == 0 || ticketCount < this.events[ data.eventID ].maxTickets ) {
|
||||
// check if enough tickets are still available
|
||||
let isExceeded = false;
|
||||
if ( ticketCount > this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] && data.count > 0 ) {
|
||||
ticketCount = this.ticketTotals[ data.eventID ] - ( this.temporaryTotals[ data.eventID ] ?? 0 ) + this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
|
||||
isExceeded = true;
|
||||
}
|
||||
let info = {};
|
||||
info[ data.eventID ] = {};
|
||||
info[ data.eventID ][ data.id ] = data;
|
||||
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
|
||||
if ( !this.temporarilySelected[ data.eventID ] ) {
|
||||
this.temporarilySelected[ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporaryTotals[ data.eventID ] ) {
|
||||
this.temporaryTotals[ data.eventID ] = 0;
|
||||
}
|
||||
this.temporarilySelected[ data.eventID ][ data.id ] = info[ data.eventID ] ? info[ data.eventID ][ data.id ] : undefined;
|
||||
this.temporaryTotals[ data.eventID ] -= this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ];
|
||||
this.temporaryTotals[ data.eventID ] += ticketCount;
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = ticketCount;
|
||||
this.countFreeSeats();
|
||||
if ( isExceeded ) {
|
||||
reject( { 'code': 409, 'message': { 'count': ticketCount } } );
|
||||
} else {
|
||||
resolve( { 'status': 'ok' } );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 418, 'message': { 'count': this.settings.maxTickets } } );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
if ( !this.allSelectedSeats[ data.eventID ] ) {
|
||||
this.allSelectedSeats[ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelected[ data.eventID ] ) {
|
||||
this.temporarilySelected[ data.eventID ] = {};
|
||||
}
|
||||
if ( this.allSelectedSeats[ data.eventID ][ data.id ] || this.temporarilySelected[ data.eventID ][ data.id ] ) {
|
||||
reject( { 'code': 409, 'message': 'ERR_ALREADY_SELECTED' } );
|
||||
} else {
|
||||
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
|
||||
let info = {};
|
||||
if ( dat[ 0 ] ) {
|
||||
info = JSON.parse( dat[ 0 ].data );
|
||||
}
|
||||
if ( !info[ data.eventID ] ) {
|
||||
info[ data.eventID ] = {};
|
||||
}
|
||||
info[ data.eventID ][ data.id ] = data;
|
||||
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'timestamp': new Date().toString(), 'data': JSON.stringify( info ) } ).then( () => {
|
||||
if ( !this.temporarilySelectedTotals[ session.id ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ] = {};
|
||||
}
|
||||
if ( !this.temporarilySelectedTotals[ session.id ][ data.eventID ] ) {
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ] = {};
|
||||
}
|
||||
if ( !this.temporaryTotals[ data.eventID ] ) {
|
||||
this.temporaryTotals[ data.eventID ] = 0;
|
||||
}
|
||||
this.temporarilySelected[ data.eventID ] = info[ data.eventID ];
|
||||
this.temporaryTotals[ data.eventID ] += 1;
|
||||
this.temporarilySelectedTotals[ session.id ][ data.eventID ][ data.id ] = 1;
|
||||
this.countFreeSeats();
|
||||
resolve( 'ok' );
|
||||
} ).catch( err => {
|
||||
console.error( err );
|
||||
} );
|
||||
} ).catch( () => {
|
||||
console.log( '[ Ticketing ] An error occurred loading data form temp database' );
|
||||
} );
|
||||
}
|
||||
}
|
||||
} else if ( call === 'deselectTicket' ) {
|
||||
db.getDataSimple( 'temp', 'user_id', session.id ).then( dat => {
|
||||
let transmit = {};
|
||||
if ( dat[ 0 ] ) {
|
||||
transmit = JSON.parse( dat[ 0 ].data );
|
||||
if ( transmit[ data.eventID ] ) {
|
||||
if ( transmit[ data.eventID ][ data.id ] ) {
|
||||
delete transmit[ data.eventID ][ data.id ];
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
|
||||
}
|
||||
if ( Object.keys( transmit[ data.eventID ] ).length < 1 ) {
|
||||
delete transmit[ data.eventID ];
|
||||
}
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
|
||||
}
|
||||
|
||||
const allSeats = this.temporarilySelected[ data.eventID ];
|
||||
for ( let seat in allSeats ) {
|
||||
if ( allSeats[ seat ].component === data.component ) {
|
||||
if ( allSeats[ seat ].id === data.id ) {
|
||||
this.temporaryTotals[ data.eventID ] -= 1;
|
||||
delete this.temporarilySelectedTotals[ session.id ][ data.eventID ][ seat ];
|
||||
delete this.temporarilySelected[ data.eventID ][ seat ];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'ERR_DATA_NOT_FOUND' } );
|
||||
return;
|
||||
}
|
||||
|
||||
db.writeDataSimple( 'temp', 'user_id', session.id, { 'user_id': session.id, 'data': JSON.stringify( transmit ) } ).then( () => {
|
||||
this.countFreeSeats();
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
reject( { 'code': 500, 'message': 'ERR_DB' } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
reject( { 'code': 500, 'message': 'ERR_DB' } );
|
||||
} );
|
||||
} else if ( call === 'resetPW' ) {
|
||||
pwHandler.resetPassword( data.email ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
if ( error.code ) {
|
||||
reject( error );
|
||||
} else {
|
||||
reject( { 'code': 500, 'message': error } );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
getReservedSeats ( event ) {
|
||||
return this.allSelectedSeats[ event ] ? Object.values( Object.assign( {}, this.allSelectedSeats[ event ], this.temporarilySelected[ event ] ) ) : ( this.temporarilySelected[ event ] ?? {} );
|
||||
}
|
||||
|
||||
countFreeSeats() {
|
||||
this.freeSeats = {};
|
||||
for ( let el in this.ticketTotals ) {
|
||||
this.freeSeats[ el ] = this.ticketTotals[ el ];
|
||||
}
|
||||
for ( let el in this.temporaryTotals ) {
|
||||
this.freeSeats[ el ] -= this.temporaryTotals[ el ];
|
||||
}
|
||||
}
|
||||
|
||||
getFreeSeatsCount() {
|
||||
return this.freeSeats;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = POSTHandler;
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* libreevent - 2fa.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const token = require( '../token.js' );
|
||||
let createSSRApp = require( 'vue' ).createSSRApp;
|
||||
let renderToString = require( 'vue/server-renderer' ).renderToString;
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
class TwoFA {
|
||||
constructor () {
|
||||
this.tokenStore = {};
|
||||
this.references = {};
|
||||
this.pwdChangeTokens = {};
|
||||
}
|
||||
|
||||
registerStandardAuthentication () {
|
||||
let tok = token.generateToken( 60 );
|
||||
while ( this.tokenStore[ tok ] ) {
|
||||
tok = token.generateToken( 60 );
|
||||
}
|
||||
this.tokenStore[ tok ] = { 'mode': 'standard' };
|
||||
return { 'token': tok };
|
||||
}
|
||||
|
||||
registerEnhancedAuthentication () {
|
||||
let tok = token.generateToken( 60 );
|
||||
while ( this.tokenStore[ tok ] ) {
|
||||
tok = token.generateToken( 60 );
|
||||
}
|
||||
let code = token.generateNumber( 6 );
|
||||
this.tokenStore[ tok ] = { 'mode': 'enhanced', 'code': code };
|
||||
return { 'code': code, 'token': tok };
|
||||
}
|
||||
|
||||
storeTokenReference ( token, sessionID ) {
|
||||
this.references[ token ] = sessionID;
|
||||
}
|
||||
|
||||
getTokenReference ( token ) {
|
||||
return this.references[ token ];
|
||||
}
|
||||
|
||||
verifyEnhanced ( token, number = '' ) {
|
||||
if ( this.tokenStore[ token ]?.mode === 'standard' ) return true;
|
||||
else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) {
|
||||
if ( this.tokenStore[ token ].code == number ) {
|
||||
delete this.tokenStore[ token ];
|
||||
return true;
|
||||
} else return false;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
verifySimple ( token ) {
|
||||
if ( this.tokenStore[ token ]?.mode === 'standard' ) {
|
||||
delete this.tokenStore[ token ];
|
||||
return 'standard';
|
||||
} else if ( this.tokenStore[ token ]?.mode === 'enhanced' ) return 'enhanced';
|
||||
else return 'invalid';
|
||||
}
|
||||
|
||||
async generateTwoFAMail ( token, ip, domain, pageName ) {
|
||||
const app = createSSRApp( {
|
||||
data() {
|
||||
return {
|
||||
token: token,
|
||||
ip: ip,
|
||||
host: domain,
|
||||
pageName: pageName,
|
||||
};
|
||||
},
|
||||
template: '' + fs.readFileSync( path.join( __dirname + '/twoFAMail.html' ) )
|
||||
} );
|
||||
|
||||
return await renderToString( app );
|
||||
}
|
||||
|
||||
async generateSignupEmail ( token, domain, pageName ) {
|
||||
const app = createSSRApp( {
|
||||
data() {
|
||||
return {
|
||||
token: token,
|
||||
host: domain,
|
||||
pageName: pageName,
|
||||
};
|
||||
},
|
||||
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/signup/signupMail.html' ) )
|
||||
} );
|
||||
|
||||
return await renderToString( app );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TwoFA;
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* libreevent - pwdmanager.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
These functions are required to verify user login and to create new users
|
||||
and to hash new passwords (if user changes password.)
|
||||
*/
|
||||
|
||||
// import and init
|
||||
const bcrypt = require( 'bcrypt' );
|
||||
const db = require( '../db/db.js' );
|
||||
const mm = require( '../mail/mailSender.js' );
|
||||
const mailManager = new mm();
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const token = require( '../token.js' );
|
||||
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' ) ) );
|
||||
|
||||
module.exports.checkpassword = function checkpassword ( email, password ) {
|
||||
return new Promise( resolve => {
|
||||
db.getDataSimple( 'user', 'email', email ).then( data => {
|
||||
if ( data ) {
|
||||
if ( data[ 0 ] ) {
|
||||
bcrypt.compare( password, data[ 0 ].pass ).then( res => {
|
||||
resolve( { 'status': res, 'twoFA': data[ 0 ].two_fa } );
|
||||
} );
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.hashPassword = ( password ) => {
|
||||
return new Promise( resolve => {
|
||||
resolve( bcrypt.hashSync( password, 10 ) );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.resetPassword = ( email ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
db.checkDataAvailability( 'users', 'email', email ).then( dat => {
|
||||
if ( dat ) {
|
||||
const newPW = token.generateToken( 20 );
|
||||
this.hashPassword( newPW ).then( hash => {
|
||||
( async () => {
|
||||
db.writeDataSimple( 'users', 'email', email, { 'pass': hash } );
|
||||
const app = createSSRApp( {
|
||||
data() {
|
||||
return {
|
||||
password: newPW,
|
||||
host: settings.yourDomain
|
||||
};
|
||||
},
|
||||
template: '' + fs.readFileSync( path.join( __dirname + '/../../ui/en/signup/pwReset.html' ) )
|
||||
} );
|
||||
|
||||
mailManager.sendMail( email, await renderToString( app ), 'Password reset', settings.mailSender );
|
||||
resolve( 'ok' );
|
||||
} )();
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'message': 'ERR_USER_NOT_FOUND' } );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
<!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>Welcome back!</h1>
|
||||
<p>It looks like someone is trying to sign in to your account at {{ pageName }}. If it was you, please click the button below to confirm the login. If not, please change your password immediately.</p>
|
||||
<p class="ip">Logging in from IP {{ ip }}.</p>
|
||||
<a :href="host + '/user/2fa?token=' + token" class="verify">Verify</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* libreevent - db.js
|
||||
*
|
||||
* Created by Janis Hutz 03/26/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
|
||||
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
|
||||
|
||||
const dbRef = {
|
||||
'user': 'libreevent_users',
|
||||
'admin': 'libreevent_admin',
|
||||
'order': 'libreevent_orders',
|
||||
'users': 'libreevent_users',
|
||||
'orders': 'libreevent_orders',
|
||||
'temp': 'libreevent_temp',
|
||||
'processingOrders': 'libreevent_processing_orders'
|
||||
};
|
||||
|
||||
const letters = [ ',', '{' ];
|
||||
|
||||
let dbh;
|
||||
|
||||
if ( settings.db === 'mysql' ) {
|
||||
const dbsoft = require( './mysqldb.js' );
|
||||
dbh = new dbsoft();
|
||||
dbh.connect();
|
||||
} else {
|
||||
const dbsoft = require( './jsondb.js' );
|
||||
dbh = new dbsoft();
|
||||
dbh.connect();
|
||||
}
|
||||
|
||||
module.exports.initDB = () => {
|
||||
( async() => {
|
||||
console.log( '[ DB ] Setting up...' );
|
||||
await dbh.resetDB();
|
||||
setTimeout( () => {
|
||||
dbh.setupDB();
|
||||
}, 2000 );
|
||||
console.log( '[ DB ] Setting up complete!' );
|
||||
} )();
|
||||
};
|
||||
|
||||
module.exports.reset = () => {
|
||||
console.log( '[ DB ] Resetting...' );
|
||||
this.writeJSONData( 'booked', {} );
|
||||
this.writeJSONData( 'eventDrafts', {} );
|
||||
this.writeJSONData( 'events', {} );
|
||||
this.writeJSONData( 'locations', {} );
|
||||
this.writeJSONData( 'events', {} );
|
||||
this.writeJSONData( 'seatplan', {} );
|
||||
this.writeJSONData( 'tickets', {} );
|
||||
console.log( '[ DB ] Reset complete!' );
|
||||
};
|
||||
|
||||
module.exports.getDataSimple = ( db, column, searchQuery ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( { 'command': 'getFilteredData', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.getData = ( db ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( { 'command': 'getAllData' }, dbRef[ db ] ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.writeDataSimple = ( db, column, searchQuery, data ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
dbh.query( { 'command': 'checkDataAvailability', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( res => {
|
||||
if ( res.length > 0 ) {
|
||||
dbh.query( { 'command': 'updateData', 'property': column, 'searchQuery': searchQuery, 'newValues': data }, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} else {
|
||||
dbh.query( { 'command': 'addData', 'data': data }, dbRef[ db ] ).then( dat => {
|
||||
resolve( dat );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
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 => {
|
||||
if ( res.length > 0 ) {
|
||||
resolve( true );
|
||||
} else {
|
||||
resolve( false );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
|
||||
module.exports.getJSONData = ( file ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + file + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
if ( data.byteLength > 0 ) {
|
||||
resolve( JSON.parse( data ) ?? {} );
|
||||
} else {
|
||||
resolve( { } );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.getJSONDataSimple = ( file, identifier ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + file + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
if ( data.byteLength > 0 ) {
|
||||
resolve( JSON.parse( data )[ identifier ] ?? {} );
|
||||
} else {
|
||||
resolve( { } );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.getJSONDataSync = ( file ) => {
|
||||
return JSON.parse( fs.readFileSync( path.join( __dirname + '/../../' + file ) ) );
|
||||
};
|
||||
|
||||
module.exports.writeJSONDataSimple = ( db, identifier, values ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
let dat = {};
|
||||
if ( data.byteLength > 0 ) {
|
||||
dat = JSON.parse( data ) ?? {};
|
||||
}
|
||||
dat[ identifier ] = values;
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.writeJSONData = ( db, data ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( data ), ( error ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
resolve( true );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.deleteJSONDataSimple = ( db, identifier ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.readFile( path.join( __dirname + '/../../data/' + db + '.json' ), ( error, data ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
let dat = {};
|
||||
if ( data.byteLength > 0 ) {
|
||||
dat = JSON.parse( data ) ?? {};
|
||||
}
|
||||
delete dat[ identifier ];
|
||||
fs.writeFile( path.join( __dirname + '/../../data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.saveSettings = ( settings ) => {
|
||||
const settingsString = JSON.stringify( settings );
|
||||
let settingsToSave = '';
|
||||
for ( let letter in settingsString ) {
|
||||
if ( letters.includes( settingsString[ letter ] ) ) {
|
||||
settingsToSave += settingsString[ letter ] + '\n\t';
|
||||
} else if ( settingsString[ letter ] === '}' ) {
|
||||
settingsToSave += '\n' + settingsString[ letter ];
|
||||
} else {
|
||||
settingsToSave += settingsString[ letter ];
|
||||
}
|
||||
}
|
||||
fs.writeFileSync( path.join( __dirname + '/../../config/settings.config.json' ), settingsToSave );
|
||||
};
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* libreevent - jsondb.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
class JSONDB {
|
||||
constructor () {
|
||||
this.db = {};
|
||||
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
|
||||
this.isSaving = false;
|
||||
this.awaitingSaving = true;
|
||||
}
|
||||
|
||||
connect () {
|
||||
let data = {};
|
||||
try {
|
||||
JSON.parse( fs.readFileSync( path.join( __dirname + '/../../data/db.json' ) ) );
|
||||
} catch ( err ) {
|
||||
console.error( '[ JSON-DB ] CRITICAL INITIALIZATION FAILURE!' + err );
|
||||
throw ( 'JSONDB failed to start!' );
|
||||
}
|
||||
this.db = data[ 'db' ] ?? { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
|
||||
this.dbIndex = data[ 'index' ] ?? { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
|
||||
this.db[ 'libreevent_temp' ] = {};
|
||||
this.saveToDisk();
|
||||
console.log( '[ JSON-DB ] Database initialized successfully' );
|
||||
return 'connection';
|
||||
}
|
||||
|
||||
async saveToDisk () {
|
||||
if ( !this.isSaving ) {
|
||||
this.awaitingSaving = false;
|
||||
this.save();
|
||||
} else {
|
||||
this.awaitingSaving = true;
|
||||
}
|
||||
}
|
||||
|
||||
save () {
|
||||
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ), ( err ) => {
|
||||
if ( err ) console.error( '[ JSON-DB ] An error occurred during saving: ' + err );
|
||||
this.isSaving = false;
|
||||
if ( this.awaitingSaving ) {
|
||||
this.saveToDisk();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
async resetDB () {
|
||||
this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
|
||||
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
|
||||
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
|
||||
}
|
||||
|
||||
async setupDB () {
|
||||
this.db = { 'libreevent_temp': {}, 'libreevent_admin': {}, 'libreevent_orders': {}, 'libreevent_users': {}, 'libreevent_processing_orders': {} };
|
||||
this.dbIndex = { 'libreevent_temp': 0, 'libreevent_admin': 0, 'libreevent_orders': 0, 'libreevent_users': 0, 'libreevent_processing_orders': 0 };
|
||||
fs.writeFile( path.join( __dirname + '/../../data/db.json' ), JSON.stringify( { 'db': this.db, 'index': this.dbIndex } ) );
|
||||
}
|
||||
|
||||
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])
|
||||
*/
|
||||
|
||||
if ( operation.command === 'getAllData' ) {
|
||||
let ret = [];
|
||||
for ( let entry in this.db[ table ] ) {
|
||||
ret.push( this.db[ table ][ entry ] );
|
||||
}
|
||||
resolve( ret );
|
||||
} else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
|
||||
let ret = [];
|
||||
for ( let entry in this.db[ table ] ) {
|
||||
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
|
||||
ret.push( this.db[ table ][ entry ] );
|
||||
}
|
||||
}
|
||||
resolve( ret );
|
||||
} else if ( operation.command === 'addData' ) {
|
||||
this.dbIndex[ table ] += 1;
|
||||
this.db[ table ][ this.dbIndex[ table ] ] = operation.data;
|
||||
this.saveToDisk();
|
||||
resolve( true );
|
||||
} else if ( operation.command === 'updateData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
for ( let entry in this.db[ table ] ) {
|
||||
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
|
||||
for ( let changed in operation.newValues ) {
|
||||
this.db[ table ][ entry ][ changed ] = operation.newValues[ changed ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.saveToDisk();
|
||||
resolve( true );
|
||||
} else if ( operation.command === 'deleteData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
for ( let entry in this.db[ table ] ) {
|
||||
if ( this.db[ table ][ entry ][ operation.property ] == operation.searchQuery ) {
|
||||
delete this.db[ table ][ entry ];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.saveToDisk();
|
||||
resolve( true );
|
||||
} else if ( operation.command === 'InnerJoin' ) {
|
||||
// TODO: Finish those when actually needed
|
||||
} else if ( operation.command === 'LeftJoin' ) {
|
||||
//
|
||||
} else if ( operation.command === 'RightJoin' ) {
|
||||
//
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSONDB;
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* libreevent - mysqldb.js
|
||||
*
|
||||
* Created by Janis Hutz 07/12/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const mysql = require( 'mysql' );
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
// If the connection does not work for you, you will need to add your ip
|
||||
// to the whitelist of the database
|
||||
|
||||
class SQLDB {
|
||||
constructor ( ) {
|
||||
this.sqlConnection = mysql.createConnection( JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/db.config.json' ) ) ) );
|
||||
}
|
||||
|
||||
connect ( ) {
|
||||
const self = this;
|
||||
this.sqlConnection.connect( function( err ) {
|
||||
if ( err ) {
|
||||
console.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack );
|
||||
return;
|
||||
}
|
||||
console.log( '[ SQL ] Connected to database successfully' );
|
||||
self.sqlConnection.query( 'TRUNCATE libreevent_temp;', error => {
|
||||
if ( error ) {
|
||||
console.error( '[ SQL ] Unable to truncate libreevent_temp table due to the following error: ' + error.code );
|
||||
} else {
|
||||
console.log( '[ SQL ] Truncated temporary data table successfully' );
|
||||
}
|
||||
} );
|
||||
return 'connection';
|
||||
} );
|
||||
}
|
||||
|
||||
disconnect ( ) {
|
||||
this.sqlConnection.end();
|
||||
}
|
||||
|
||||
async resetDB ( ) {
|
||||
this.sqlConnection.query( 'DROP TABLE libreevent_orders;', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
|
||||
this.sqlConnection.query( 'DROP TABLE libreevent_users;', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
|
||||
this.sqlConnection.query( 'DROP TABLE libreevent_admin;', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
|
||||
this.sqlConnection.query( 'DROP TABLE libreevent_temp;', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
|
||||
this.sqlConnection.query( 'DROP TABLE libreevent_processing_orders;', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_BAD_TABLE_ERROR' ) throw error;
|
||||
return 'done';
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
async setupDB ( ) {
|
||||
this.sqlConnection.query( 'SELECT @@default_storage_engine;', ( error, results ) => {
|
||||
if ( error ) throw error;
|
||||
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) throw '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 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, timestamp 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;
|
||||
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;
|
||||
this.sqlConnection.query( 'CREATE TABLE libreevent_temp ( entry_id INT NOT NULL AUTO_INCREMENT, user_id TINYTEXT, data VARCHAR( 60000 ), timestamp TINYTEXT, PRIMARY KEY ( entry_id ) );', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||
this.sqlConnection.query( 'CREATE TABLE libreevent_processing_orders ( entry_id INT NOT NULL AUTO_INCREMENT, user_id TINYTEXT, data VARCHAR( 60000 ), timestamp TINYTEXT, PRIMARY KEY ( entry_id ) );', ( error ) => {
|
||||
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
|
||||
return 'DONE';
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
- 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)
|
||||
- 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!
|
||||
*/
|
||||
let command = '';
|
||||
if ( operation.command === 'getAllData' ) {
|
||||
command = 'SELECT * FROM ' + table;
|
||||
} else if ( operation.command === 'getFilteredData' || operation.command === 'checkDataAvailability' ) {
|
||||
command = 'SELECT * FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
} else if ( operation.command === 'fullCustomCommand' ) {
|
||||
command = operation.query;
|
||||
} else if ( operation.command === 'addData' ) {
|
||||
let keys = '';
|
||||
let values = '';
|
||||
for ( let key in operation.data ) {
|
||||
keys += String( key ) + ', ';
|
||||
values += this.sqlConnection.escape( String( operation.data[ key ] ) ) + ', ' ;
|
||||
}
|
||||
command = 'INSERT INTO ' + table + ' (' + keys.slice( 0, keys.length - 2 ) + ') VALUES (' + values.slice( 0, values.length - 2 ) + ');';
|
||||
} else if ( operation.command === 'updateData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
command = 'UPDATE ' + table + ' SET ';
|
||||
let updatedValues = '';
|
||||
for ( let value in operation.newValues ) {
|
||||
updatedValues += value + ' = ' + this.sqlConnection.escape( String( operation.newValues[ value ] ) ) + ', ';
|
||||
}
|
||||
command += updatedValues.slice( 0, updatedValues.length - 2 );
|
||||
command += ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
}
|
||||
} else if ( operation.command === 'deleteData' ) {
|
||||
if ( !operation.property || !operation.searchQuery ) reject( 'Refusing to run destructive command: Missing Constraints' );
|
||||
else {
|
||||
command = 'DELETE FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery );
|
||||
}
|
||||
} else if ( operation.command === 'InnerJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' INNER JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
|
||||
} else if ( operation.command === 'LeftJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' LEFT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
|
||||
} else if ( operation.command === 'RightJoin' ) {
|
||||
command = 'SELECT ' + operation.selection + ' FROM ' + table + ' WHERE ' + operation.property + ' = ' + this.sqlConnection.escape( operation.searchQuery ) + ' RIGHT JOIN ' + operation.secondTable + ' ON ' + operation.matchingParam;
|
||||
}
|
||||
this.sqlConnection.query( command, ( error, results ) => {
|
||||
if ( error ) reject( error );
|
||||
resolve( results );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SQLDB;
|
||||
@@ -1,35 +0,0 @@
|
||||
const path = require( 'path' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.get( '/eventAssets/:image', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../assets/events/' + req.params.image ) );
|
||||
} );
|
||||
|
||||
app.get( '/otherAssets/:image', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../assets/' + req.params.image ) );
|
||||
} );
|
||||
|
||||
app.get( '/supportFiles/:file', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + settings.startPage + '/supportFiles/' + req.params.file ) );
|
||||
} );
|
||||
|
||||
app.get( '/startPage/helperFunction', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../ui/home/helper.js' ) );
|
||||
} );
|
||||
|
||||
app.get( '/startPage/mainStyle', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../ui/home/main.css' ) );
|
||||
} );
|
||||
|
||||
app.get( '/startPage/assets/:image', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + settings.startPage + '/assets/' + req.params.image ) );
|
||||
} );
|
||||
|
||||
app.get( '/startPage/preview/:template', ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
res.sendFile( path.join( __dirname + '/../ui/home/templates/' + req.params.template + '/index.html' ) );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* LANGUAGE SCHOOL HOSSEGOR - Booking system
|
||||
* mailManager.js
|
||||
*
|
||||
* Developed 2022 by Janis Hutz
|
||||
*
|
||||
*/
|
||||
// import and init of nodemailer middleware
|
||||
const mailer = require( 'nodemailer' );
|
||||
const html2text = require( 'html-to-text' );
|
||||
|
||||
const db = require( '../db/db.js' );
|
||||
|
||||
let transporter = mailer.createTransport( db.getJSONDataSync( '/config/mail.config.json' ) );
|
||||
|
||||
|
||||
class MailManager {
|
||||
constructor () {
|
||||
this.options = {
|
||||
wordwrap: 130
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
This method sends a mail with recipient, html, subject and sender as arguments
|
||||
*/
|
||||
sendMail ( recipient, html, subject, sender ) {
|
||||
let text = html2text.convert( html, this.options );
|
||||
let mailOptions = {
|
||||
from: sender,
|
||||
to: recipient,
|
||||
subject: subject,
|
||||
html: html,
|
||||
text: text,
|
||||
};
|
||||
|
||||
transporter.sendMail( mailOptions, function ( error ) {
|
||||
if ( error ) {
|
||||
console.error( error );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
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,
|
||||
to: recipient,
|
||||
subject: subject,
|
||||
html: html,
|
||||
text: text,
|
||||
attachments: attachments
|
||||
};
|
||||
|
||||
transporter.sendMail( mailOptions, function ( error ) {
|
||||
if ( error ) {
|
||||
console.error( error );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MailManager;
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* libreevent - paymentRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 08/02/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
// const ph = require( './paymentHandler.js' );
|
||||
// const paymentHandler = new ph();
|
||||
|
||||
module.exports = ( app ) => {
|
||||
app.get( '/payments/canceled', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../../ui/en/payments/canceled.html' ) );
|
||||
} );
|
||||
|
||||
app.get( '/payments/failed', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/../../ui/en/payments/failed.html' ) );
|
||||
} );
|
||||
|
||||
app.get( '/tickets/tickets.pdf', ( req, res ) => {
|
||||
if ( req.session.lastOrderID ) {
|
||||
fs.readFile( path.join( __dirname + '/../tickets/store/' + req.session.lastOrderID + '.pdf' ), ( error, data ) => {
|
||||
if ( error ) res.sendFile( path.join( __dirname + '/../../ui/en/errors/404.html' ) );
|
||||
else res.send( data );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
# Plugins
|
||||
|
||||
If you want to create a new plugin for libreevent, please follow our guide and guidelines in the official documentation [here](https://libreevent.janishutz.com/docs/contributing/plugins)
|
||||
|
||||
Each plugin should have a plugin.json file that uses the layout of the plugin.json file in this directory. As future libreevent might change what is required by the plugin.json file, please follow this repository to get news when this is about to happen. To retain backwards compatibility, we will for as long as possible not remove anything from the plugin.json files as possible, which means you can already update your plugin.json file before the next version of libreevent is released. The plugin.json file already contains a "mainPluginURL" parameter which currently is not in use.
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* libreevent - manager.js
|
||||
*
|
||||
* Created by Janis Hutz 07/25/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
This is the plugin manager. It is responsible for installing, updating and uninstalling plugins.
|
||||
*/
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
class PluginManager {
|
||||
constructor ( settings ) {
|
||||
this.paymentGateway = settings.payments;
|
||||
this.pluginDetails = {};
|
||||
fs.readdir( path.join( __dirname + '/others' ), ( err, ls ) => {
|
||||
for ( let file in ls ) {
|
||||
const pluginDetail = JSON.parse( fs.readFileSync( path.join( __dirname + '/others/' + ls[ file ] + '/plugin.json' ) ) );
|
||||
this.pluginDetails[ ls[ file ] ] = pluginDetail;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
getPlugins () {
|
||||
return this.pluginDetails;
|
||||
}
|
||||
|
||||
loadPaymentGatewaySettings () {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
this.paymentGateway = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) ).payments;
|
||||
fs.readFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/configOptions.json' ), ( err, optionsBuffer ) => {
|
||||
if ( err ) reject( err );
|
||||
fs.readFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/config.payments.json' ), ( err, configBuffer ) => {
|
||||
if ( err ) reject( err );
|
||||
let options, config;
|
||||
try {
|
||||
options = JSON.parse( optionsBuffer );
|
||||
config = JSON.parse( configBuffer );
|
||||
} catch ( err ) {
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
let f = options;
|
||||
for ( let s in f ) {
|
||||
f[ s ][ 'value' ] = config[ s ];
|
||||
}
|
||||
resolve( { 'data': f, 'gateway': this.paymentGateway } );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
savePaymentGatewaySettings ( settings ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.writeFile( path.join( __dirname + '/payments/' + this.paymentGateway + '/config.payments.json' ), JSON.stringify( settings ), {}, ( err ) => {
|
||||
if ( err ) reject( err );
|
||||
resolve( 'ok' );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PluginManager;
|
||||
@@ -1,74 +0,0 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: rgb(180, 191, 255);
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 48
|
||||
}
|
||||
|
||||
.mail-app {
|
||||
width: 80%;
|
||||
margin-left: 5%;
|
||||
margin-top: 1%;
|
||||
background-color: rgb(144, 160, 255);
|
||||
border-radius: 50px;
|
||||
padding: 5%;
|
||||
}
|
||||
|
||||
#toolbar, #editor {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#top-bar {
|
||||
color: white;
|
||||
background-color: rgb(0, 8, 53);
|
||||
margin-bottom: 0;
|
||||
grid-area: header;
|
||||
height: 10vh;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
#spacer {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.top-toolbar {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10%;
|
||||
gap: 10%;
|
||||
}
|
||||
|
||||
.top-buttons {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
border-color: white;
|
||||
border-style: solid;
|
||||
padding: 0.5%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top-buttons:hover {
|
||||
color: blue;
|
||||
border-color: blue;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Include Quill stylesheet -->
|
||||
<meta charset="utf-8">
|
||||
<title>New Mail :: Webmail | Language School Hossegor - Admin</title>
|
||||
<link rel="stylesheet" href="/admin/plugins/newsletter/css/mailCompose.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||
<link href="https://cdn.quilljs.com/1.0.0/quill.snow.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200">
|
||||
</head>
|
||||
<body>
|
||||
<div id="top-bar">
|
||||
<div class="top-toolbar">
|
||||
<a href="/admin/plugins" title="Back to admin panel" id="back" class="top-buttons"><span class="material-symbols-outlined">arrow_back</span></a>
|
||||
</div>
|
||||
<p id="title">Newsletter plugin - libreevent</p>
|
||||
<p id="spacer"></p>
|
||||
</div>
|
||||
<div class="mail-app">
|
||||
<h1>New newsletter</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="subject">Subject:</label>
|
||||
</td><td>
|
||||
<input type="text" name="subject" id="subject"><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Create the toolbar container -->
|
||||
<div id="toolbar">
|
||||
<span class="ql-formats">
|
||||
<select class="ql-font" title="Fonts">
|
||||
<option selected="" title="Default"></option>
|
||||
<option value="serif" title="Serif"></option>
|
||||
<option value="monospace" title="Monospace"></option>
|
||||
</select>
|
||||
<select class="ql-size" title="Font size">
|
||||
<option value="small" title="Small"></option>
|
||||
<option selected="" title="Default"></option>
|
||||
<option value="large" title="Large"></option>
|
||||
<option value="huge" title="Huge"></option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="ql-formats">
|
||||
<button class="ql-bold" title="Bold"></button>
|
||||
<button class="ql-italic" title="Italic"></button>
|
||||
<button class="ql-underline" title="Underlined"></button>
|
||||
<button class="ql-strike" title="Strikethrough"></button>
|
||||
</span>
|
||||
<span class="ql-formats">
|
||||
<select class="ql-color" title="Text colour"></select>
|
||||
<select class="ql-background" title="Background colour"></select>
|
||||
</span>
|
||||
<span class="ql-formats">
|
||||
<button class="ql-list" value="ordered" title="Ordered list"></button>
|
||||
<button class="ql-list" value="bullet" title="Bullet points"></button>
|
||||
<select class="ql-align" title="Alignment">
|
||||
<option selected="" title="left"></option>
|
||||
<option value="center" title="center"></option>
|
||||
<option value="right" title="right"></option>
|
||||
<option value="justify" title="block"></option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="ql-formats">
|
||||
<button class="ql-link" title="Insert link"></button>
|
||||
<button class="ql-image" title="Insert image"></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Create the editor container -->
|
||||
<div id="editor">
|
||||
</div>
|
||||
|
||||
<button onclick="sendMail()"><span class="material-symbols-outlined">send</span>Send</button>
|
||||
|
||||
</div>
|
||||
<!-- Include the Quill library -->
|
||||
<script src="https://cdn.quilljs.com/1.0.0/quill.js"></script>
|
||||
|
||||
<!-- Initialize Quill editor -->
|
||||
<script>
|
||||
var editor = new Quill( '#editor', {
|
||||
modules: { toolbar: '#toolbar' },
|
||||
theme: 'snow',
|
||||
} );
|
||||
|
||||
function sendMail () {
|
||||
if ( !document.getElementById( 'subject' ).value ) {
|
||||
alert( 'An email subject is required!' );
|
||||
return;
|
||||
}
|
||||
let dat = { 'subject': document.getElementById( 'subject' ).value, 'message': document.getElementsByClassName( 'ql-editor' )[0].innerHTML };
|
||||
let options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( dat ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
},
|
||||
};
|
||||
fetch( '/admin/plugins/newsletter/send', options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
alert( 'Newsletter sent successfully' );
|
||||
} else if ( res.status === 403 ) {
|
||||
alert( 'It appears that you have been logged out or have logged out somewhere else. Please ensure that you are logged in and try again!' );
|
||||
} else {
|
||||
alert( 'There was an error sending the mail. Please try again. If the error persists, please contact the developer and tell this status code: ' + res.status );
|
||||
}
|
||||
} );
|
||||
document.getElementById( 'subject' ).value = '';
|
||||
document.getElementsByClassName( 'ql-editor' )[0].innerHTML = '';
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Unsubscribe from Newsletter</title>
|
||||
<link rel="stylesheet" href="/css/errorstyle.css">
|
||||
<style>
|
||||
.input {
|
||||
width: 40%;
|
||||
padding: 0.5%;
|
||||
font-size: 120%;
|
||||
border-radius: 15px;
|
||||
margin: 2%;
|
||||
margin-bottom: 4%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Unsubscribe from newsletter</h1>
|
||||
<form action="/mail/unsubscribe/go?lang=en" method="post" class="unsub">
|
||||
<label for="mail" style="font-size: 120%;">Your email address</label><br>
|
||||
<input type="email" name="mail" id="mail" class="input" required><br>
|
||||
|
||||
<input type="submit" value="Unsubscribe" class="button" style="font-size: 130%;">
|
||||
</form>
|
||||
<a href="?lang=de" class="button" style="margin-top: 4%;">DE</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,33 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Unsubscribed from Newsletter</title>
|
||||
<link rel="stylesheet" href="/css/errorstyle.css">
|
||||
<style>
|
||||
.input {
|
||||
width: 40%;
|
||||
padding: 0.5%;
|
||||
font-size: 120%;
|
||||
border-radius: 15px;
|
||||
margin: 2%;
|
||||
margin-bottom: 4%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Unsubscribed from newsletter</h1>
|
||||
<p>You have successfully unsubscribed from the newsletter.</p>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout( redirect, 7000 );
|
||||
|
||||
function redirect () {
|
||||
location.href = '/';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Error - Unsubscribe from Newsletter</title>
|
||||
<link rel="stylesheet" href="/css/errorstyle.css">
|
||||
<style>
|
||||
.input {
|
||||
width: 40%;
|
||||
padding: 0.5%;
|
||||
font-size: 120%;
|
||||
border-radius: 15px;
|
||||
margin: 2%;
|
||||
margin-bottom: 4%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>An error occurred whilst unsubscribing from the newsletter</h1>
|
||||
<p>Please check your entries and try again!</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* libreevent - newsletterRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require( 'path' );
|
||||
const mm = require( './sender.js' );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
const sendMail = new mm( settings );
|
||||
app.get( '/admin/plugins/newsletter', ( request, response ) => {
|
||||
if ( request.session.loggedInAdmin ) {
|
||||
response.sendFile( path.join( __dirname + '/html/compose.html' ) );
|
||||
} else {
|
||||
response.status( 403 ).send( 'unauthenticated' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/admin/plugins/newsletter/css/:file', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/css/' + req.params.file ) );
|
||||
} );
|
||||
|
||||
app.post( '/admin/plugins/newsletter/send', bodyParser.json(), ( request, response ) => {
|
||||
if ( request.session.loggedInAdmin ) {
|
||||
response.send( 'ok' );
|
||||
sendMail.send( request.body.message, request.body.subject );
|
||||
} else {
|
||||
response.status( 403 ).send( 'unauthenticated' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/mail/unsubscribe', ( request, response ) => {
|
||||
response.sendFile( path.join( __dirname + '/html/unsubscribe.html' ) );
|
||||
} );
|
||||
|
||||
app.post( '/mail/unsubscribe/go', ( request, response ) => {
|
||||
if ( !request.body.mail ) {
|
||||
response.sendFile( path.join( __dirname + '/html/unsubscribeError.html' ) );
|
||||
} else {
|
||||
sendMail.unsubscribe( request.body.mail );
|
||||
response.sendFile( path.join( __dirname + '/html/unsubscribeComplete.html' ) );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"pluginName": "Newsletter",
|
||||
"pluginDescription": "Send newsletters to your customers using a file editor",
|
||||
"creator": "Janis Hutz",
|
||||
"maintainer": "Janis Hutz",
|
||||
"pluginWebsite": "https://libreevent.janishutz.com/plugins/newsletter",
|
||||
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/newsletter",
|
||||
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/newsletter",
|
||||
"settingsURL": "/admin/plugins/newsletter",
|
||||
"mainPluginURL": "/admin/plugins/newsletter",
|
||||
"logo": "",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* libreevent - sender.js
|
||||
*
|
||||
* Created by Janis Hutz 09/16/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const mm = require( '../../../mail/mailSender.js' );
|
||||
const mailManager = new mm();
|
||||
const db = require( '../../../db/db.js' );
|
||||
|
||||
|
||||
class sendMail {
|
||||
constructor( settings ) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
send ( message, subject ) {
|
||||
db.getDataSimple( 'users', 'marketing', 'true' ).then( users => {
|
||||
for ( let user in users ) {
|
||||
mailManager.sendMail( users[ user ].email, message, subject, this.settings.mailSender );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
unsubscribe ( mail ) {
|
||||
db.writeDataSimple( 'users', 'email', mail, { 'marketing': false } );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sendMail;
|
||||
@@ -1,47 +0,0 @@
|
||||
#popup, #popup-data {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: rgba( 0, 0, 0, 0.5 );
|
||||
}
|
||||
|
||||
.popup-positioning {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.popup-main {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5%;
|
||||
background-color: rgb(34, 34, 34);
|
||||
color: white;
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
html {
|
||||
height: 98%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: monospace;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(114, 164, 173);
|
||||
}
|
||||
|
||||
.title-area {
|
||||
font-size: 1000%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.title-area h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 70%;
|
||||
height: 40%;
|
||||
font-size: 150%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
padding: 2%;
|
||||
border-radius: 10px;
|
||||
resize: vertical;
|
||||
font-size: 90%;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
nig
|
||||
cum
|
||||
pron
|
||||
jap
|
||||
xxx
|
||||
cok
|
||||
kkk
|
||||
Africoon
|
||||
akata
|
||||
Beaner
|
||||
Camel jockey
|
||||
Chink
|
||||
Coon
|
||||
Coonass
|
||||
Dune Coon
|
||||
Gook
|
||||
Jungle Bunny
|
||||
Niglet
|
||||
Nignog
|
||||
Porch Monkey
|
||||
Towel Head
|
||||
Turk
|
||||
Wetback
|
||||
Wigger
|
||||
Cock
|
||||
Fag
|
||||
Kock
|
||||
Pecker
|
||||
Tit
|
||||
Wang
|
||||
wank
|
||||
Willy
|
||||
Willies
|
||||
Anal
|
||||
Slut
|
||||
Retard
|
||||
Retarded
|
||||
niga
|
||||
Raped
|
||||
Rape
|
||||
Rapist
|
||||
Semen
|
||||
Cums
|
||||
Cum
|
||||
Fag
|
||||
Fags
|
||||
Tit
|
||||
Porn
|
||||
Slut
|
||||
Smut
|
||||
Teets
|
||||
Tits
|
||||
Jiz
|
||||
doosh
|
||||
Dick
|
||||
Cunt
|
||||
Boob
|
||||
scat
|
||||
slut
|
||||
twat
|
||||
pube
|
||||
pubes
|
||||
twat
|
||||
retards
|
||||
rimming
|
||||
milf
|
||||
nazi
|
||||
orgy
|
||||
porn
|
||||
pussy
|
||||
niggar
|
||||
Nigga
|
||||
Nigger
|
||||
assfucker
|
||||
assfucka
|
||||
b00b
|
||||
b00bs
|
||||
ballsack
|
||||
beastial
|
||||
beastiality
|
||||
bestial
|
||||
bestiality
|
||||
blow job
|
||||
blowjob
|
||||
blowjobs
|
||||
boner
|
||||
boobs
|
||||
booobs
|
||||
boooobs
|
||||
booooobs
|
||||
buttplug
|
||||
c0ck
|
||||
c0cksucker
|
||||
chink
|
||||
cl1t
|
||||
clit
|
||||
clitoris
|
||||
clits
|
||||
cock-sucker
|
||||
cockface
|
||||
cockhead
|
||||
cockmunch
|
||||
cockmuncher
|
||||
cocks
|
||||
cocksuck
|
||||
cocksucked
|
||||
cocksucker
|
||||
cocksucking
|
||||
cocksucks
|
||||
cocksuka
|
||||
cocksukka
|
||||
cokmuncher
|
||||
coksucka
|
||||
cummer
|
||||
cumshot
|
||||
cunilingus
|
||||
cunillingus
|
||||
cunnilingus
|
||||
cuntlick
|
||||
cuntlicker
|
||||
cuntlicking
|
||||
cunts
|
||||
cyalis
|
||||
cyberfuc
|
||||
cyberfuck
|
||||
cyberfucked
|
||||
cyberfucker
|
||||
cyberfuckers
|
||||
cyberfucking
|
||||
d1ck
|
||||
dickhead
|
||||
dildo
|
||||
dildos
|
||||
dog-fucker
|
||||
doggin
|
||||
dogging
|
||||
donkeyribber
|
||||
doosh
|
||||
duche
|
||||
Douche
|
||||
ejaculate
|
||||
ejaculated
|
||||
ejaculates
|
||||
ejaculating
|
||||
ejaculatings
|
||||
ejaculation
|
||||
ejakulate
|
||||
F4nny
|
||||
fagging
|
||||
faggitt
|
||||
faggot
|
||||
faggs
|
||||
fagot
|
||||
fagots
|
||||
fatass
|
||||
felching
|
||||
fellate
|
||||
fellatio
|
||||
fingerfuck
|
||||
fingerfucked
|
||||
fingerfucker
|
||||
fingerfuckers
|
||||
fingerfucking
|
||||
fingerfucks
|
||||
fistfuck
|
||||
fistfucked
|
||||
fistfucker
|
||||
fistfuckers
|
||||
fistfucking
|
||||
fistfuckings
|
||||
fistfucks
|
||||
fuckhead
|
||||
fuckheads
|
||||
fuckingshitmotherfucker
|
||||
fuckme
|
||||
fuckwhit
|
||||
fuckwit
|
||||
fukwit
|
||||
fukwhit
|
||||
gangbang
|
||||
gangbanged
|
||||
gangbangs
|
||||
gaysex
|
||||
goatse
|
||||
hardcoresex
|
||||
horniest
|
||||
horny
|
||||
hotsex
|
||||
jack-off
|
||||
jackoff
|
||||
jerk-off
|
||||
jism
|
||||
jizm
|
||||
jizz
|
||||
kawk
|
||||
kondum
|
||||
kondums
|
||||
kummer
|
||||
humming
|
||||
kunilingus
|
||||
l3i+ch
|
||||
l3itch
|
||||
labia
|
||||
m0f0
|
||||
m0fo
|
||||
m45terbate
|
||||
ma5terb8
|
||||
ma5terbate
|
||||
masochist
|
||||
master-bate
|
||||
masterb8
|
||||
masterbat*
|
||||
masterbat3
|
||||
masterbate
|
||||
masterbation
|
||||
masterbations
|
||||
masturbate
|
||||
nig
|
||||
n1g
|
||||
n1gga
|
||||
n1gger
|
||||
nazi
|
||||
nigg3r
|
||||
nigg4h
|
||||
nigga
|
||||
niggah
|
||||
niggas
|
||||
niggaz
|
||||
nigger
|
||||
niggers
|
||||
nutsack
|
||||
orgasim
|
||||
orgasims
|
||||
orgasm
|
||||
orgasms
|
||||
p0rn
|
||||
penis
|
||||
phonesex
|
||||
penisfucker
|
||||
phuck
|
||||
phuk
|
||||
phuked
|
||||
phuking
|
||||
phukked
|
||||
phukking
|
||||
phuks
|
||||
pigfucker
|
||||
pissoff
|
||||
porno
|
||||
pornography
|
||||
pornos
|
||||
pusse
|
||||
pussi
|
||||
pussies
|
||||
pussy
|
||||
pussys
|
||||
rectum
|
||||
rimjaw
|
||||
sadist
|
||||
schlong
|
||||
scrotum
|
||||
shaggin
|
||||
shagging
|
||||
shitdick
|
||||
shited
|
||||
shitfuck
|
||||
shithead
|
||||
sluts
|
||||
smegma
|
||||
spunk
|
||||
t1tt1e5
|
||||
t1tties
|
||||
testical
|
||||
testicle
|
||||
titfuck
|
||||
tittie5
|
||||
tittiefucker
|
||||
titties
|
||||
tittyfuck
|
||||
tittywank
|
||||
titwank
|
||||
tw4t
|
||||
twathead
|
||||
twatty
|
||||
twunt
|
||||
twunter
|
||||
v14gra
|
||||
v1gra
|
||||
vagina
|
||||
viagra
|
||||
vulva
|
||||
w00se
|
||||
wanker
|
||||
wanky
|
||||
whore
|
||||
xrated
|
||||
hitler
|
||||
putin
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,108 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=7">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Settings - Polls :: libreevent-plugin</title>
|
||||
<link rel="stylesheet" href="/polls/css/style.css">
|
||||
<link rel="stylesheet" href="/polls/css/popup.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24
|
||||
}
|
||||
|
||||
.voting-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.voting {
|
||||
border-radius: 500px;
|
||||
border: 1px black solid;
|
||||
font-size: 150%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.voting-counter {
|
||||
margin: 0;
|
||||
font-size: 150%;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 30vw;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
margin-bottom: 1vh;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.poll {
|
||||
border: black 2px solid;
|
||||
padding: 1% 10%;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content" id="app">
|
||||
<h1>Polls plugin - Settings</h1>
|
||||
<div style="margin-bottom: 1%;">
|
||||
<a href="/admin/plugins">Back to the admin panel</a><br>
|
||||
<button @click="addPoll();">Add new poll</button>
|
||||
</div>
|
||||
<div v-for="poll in polls" class="poll">
|
||||
<h3>{{ poll.display }}</h3>
|
||||
<p>{{ poll.comment }}</p>
|
||||
<button @click="editPoll( poll.id )">Edit</button>
|
||||
</div>
|
||||
<div id="popup">
|
||||
<div class="popup-positioning">
|
||||
<div class="popup-main">
|
||||
<form id="popup-message">
|
||||
<h2 style="font-size: 200%;">{{ operation }} poll</h2>
|
||||
<label for="title">Poll title</label><br>
|
||||
<input type="text" v-model="newPoll.display" name="title" id="title" class="input"><br>
|
||||
<label for="id">Poll id</label><br>
|
||||
<input type="text" v-model="newPoll.id" name="id" id="id" class="input"><br>
|
||||
<label for="comment">Comments</label><br>
|
||||
<textarea type="text" v-model="newPoll.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
|
||||
<label for="id">Allow adding suggestions</label>
|
||||
<input type="checkbox" v-model="newPoll.allowAdding" name="allowAdding" id="allowAdding"><br>
|
||||
</form>
|
||||
<button @click="save()" class="submit">Save</button>
|
||||
<button @click="closePopup()" class="submit">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="/polls/js/settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,146 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=7">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Polls :: libreevent-plugin</title>
|
||||
<link rel="stylesheet" href="/polls/css/style.css">
|
||||
<link rel="stylesheet" href="/polls/css/popup.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24
|
||||
}
|
||||
|
||||
.voting-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.voting {
|
||||
border-radius: 500px;
|
||||
border: 1px black solid;
|
||||
font-size: 150%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.voting-counter {
|
||||
margin: 0;
|
||||
font-size: 150%;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 30vw;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
margin-bottom: 1vh;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.entry {
|
||||
border: black 2px solid;
|
||||
padding: 1% 10%;
|
||||
width: 40vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.comment {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 5vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content" id="app">
|
||||
<div class="wrapper" v-if="hasLoadedBasics && hasLoadedVotes">
|
||||
<div v-if="votingDetails.display" class="wrapper">
|
||||
<h1>Voting on {{ votingDetails.display ?? 'untitled' }}</h1>
|
||||
<p v-if="votingDetails.description" class="comment">{{ votingDetails.description }}</p>
|
||||
<div style="margin-bottom: 0.5%;" v-if="votingDetails.allowAdding">
|
||||
<button onclick="location.href = '/'">Back to website</button>
|
||||
<button @click="addSuggestion();">Add suggestion</button>
|
||||
</div>
|
||||
<select v-model="sorting" style="margin-bottom: 1%;">
|
||||
<option value="newest">Newest</option>
|
||||
<option value="oldest">Oldest</option>
|
||||
<option value="nameUp">Alphabetically (A-Z)</option>
|
||||
<option value="nameDown">Alphabetically (Z-A)</option>
|
||||
<option value="mostVoted">Most popular</option>
|
||||
<option value="leastVoted">Least popular</option>
|
||||
</select>
|
||||
<div v-for="entry in orderedVotes" class="entry">
|
||||
<h3>{{ entry.title }}</h3>
|
||||
<p>{{ entry.comment }}</p>
|
||||
<div class="voting-wrapper">
|
||||
<span class="material-symbols-outlined voting" @click="vote( 'up', entry.id )" :class="votedOn[ entry.id ] === 'up' ? 'selected' : ''">arrow_upward</span>
|
||||
<p class="voting-counter">{{ entry.count ?? 0 }}</p>
|
||||
<span class="material-symbols-outlined voting" @click="vote( 'down', entry.id )" :class="votedOn[ entry.id ] === 'down' ? 'selected' : ''">arrow_downward</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup">
|
||||
<div class="popup-positioning">
|
||||
<div class="popup-main">
|
||||
<form id="popup-message">
|
||||
<h2 style="font-size: 200%;">Add new suggestion</h2>
|
||||
<label for="title">Suggestion title</label><br>
|
||||
<input type="text" v-model="newSuggestion.title" name="title" id="title" class="input"><br>
|
||||
<label for="title">Comments</label><br>
|
||||
<textarea type="text" v-model="newSuggestion.comment" name="comment" id="comment" class="input" rows="5"></textarea><br>
|
||||
</form>
|
||||
<button @click="save()" class="submit">Add</button>
|
||||
<button @click="closePopup()" class="submit">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="display: flex; justify-content: center; align-items: center; height: 100vh; font-size: 110%;">
|
||||
<h1>This poll does not exist!</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="/polls/js/voting.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
/* eslint-disable no-undef */
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp( {
|
||||
data() {
|
||||
return {
|
||||
polls: {},
|
||||
newPoll: {},
|
||||
operation: 'Add new',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
fetch( '/admin/plugins/polls/getData' ).then( response => {
|
||||
response.json().then( data => {
|
||||
this.polls = data;
|
||||
this.newPoll = {};
|
||||
} );
|
||||
} );
|
||||
},
|
||||
save() {
|
||||
if ( this.newPoll.comment && this.newPoll.display && this.newPoll.id ) {
|
||||
this.polls[ this.newPoll.id ] = this.newPoll;
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( this.polls ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
},
|
||||
};
|
||||
fetch( '/admin/plugins/polls/save', fetchOptions ).then( response => {
|
||||
if ( response.status !== 200 ) {
|
||||
alert( 'there was an error updating' );
|
||||
}
|
||||
} );
|
||||
this.closePopup();
|
||||
this.getData();
|
||||
} else {
|
||||
alert( 'Not all required fields are filled out!' );
|
||||
}
|
||||
},
|
||||
closePopup() {
|
||||
$( '#popup' ).fadeOut( 500 );
|
||||
$( 'body' ).removeClass( 'menuOpen' );
|
||||
this.getData();
|
||||
},
|
||||
addPoll () {
|
||||
this.newPoll = { 'allowAdding': true };
|
||||
this.operation = 'Add new';
|
||||
$( '#popup' ).fadeIn( 500 );
|
||||
$( 'body' ).addClass( 'menuOpen' );
|
||||
},
|
||||
editPoll ( pollID ) {
|
||||
this.operation = 'Edit';
|
||||
this.newPoll = this.polls[ pollID ];
|
||||
$( '#popup' ).fadeIn( 500 );
|
||||
$( 'body' ).addClass( 'menuOpen' );
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getData();
|
||||
}
|
||||
} ).mount( '#app' );
|
||||
@@ -1,184 +0,0 @@
|
||||
/* eslint-disable no-undef */
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp( {
|
||||
data() {
|
||||
return {
|
||||
entries: {},
|
||||
newSuggestion: {},
|
||||
votingDetails: {},
|
||||
votedOn: {},
|
||||
hasLoadedBasics: false,
|
||||
hasLoadedVotes: false,
|
||||
sorting: 'newest',
|
||||
userIdentifier: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
orderedVotes() {
|
||||
if ( this.sorting === 'oldest' ) {
|
||||
return Object.values( this.entries );
|
||||
} else if ( this.sorting === 'newest' ) {
|
||||
const ent = Object.keys( this.entries ).reverse();
|
||||
let ret = [];
|
||||
for ( let entry in ent ) {
|
||||
ret.push( this.entries[ ent[ entry ] ] );
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
let ent = Object.keys( this.entries ).sort( ( a, b ) => {
|
||||
if ( this.sorting === 'nameUp' ) {
|
||||
return this.entries[ a ].title.localeCompare( this.entries[ b ].title );
|
||||
} else if ( this.sorting === 'nameDown' ) {
|
||||
return this.entries[ b ].title.localeCompare( this.entries[ a ].title );
|
||||
} else if ( this.sorting === 'mostVoted' ) {
|
||||
return this.entries[ b ].count - this.entries[ a ].count;
|
||||
} else if ( this.sorting === 'leastVoted' ) {
|
||||
return this.entries[ a ].count - this.entries[ b ].count;
|
||||
}
|
||||
} );
|
||||
let ret = [];
|
||||
for ( let entry in ent ) {
|
||||
ret.push( this.entries[ ent[ entry ] ] );
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
fetch( '/polls/get/' + location.pathname.substring( 7 ) ).then( response => {
|
||||
response.json().then( data => {
|
||||
this.entries = data;
|
||||
this.fingerprint();
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'fingerprint': this.userIdentifier } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
},
|
||||
};
|
||||
fetch( '/voting/getVotedOn/' + location.pathname.substring( 8 ), fetchOptions ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.json().then( json => {
|
||||
this.votedOn = JSON.parse( localStorage.getItem( 'itemsVotedOn' ) ?? '{}' );
|
||||
for ( let el in json ) {
|
||||
if ( json[ el ] === 1 ) {
|
||||
this.votedOn[ json[ el ] ] = 'up';
|
||||
} else if ( json[ el ] === -1 ) {
|
||||
this.votedOn[ json[ el ] ] = 'down';
|
||||
}
|
||||
}
|
||||
localStorage.setItem( 'itemsVotedOn', JSON.stringify( this.votedOn ) );
|
||||
this.hasLoadedVotes = true;
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
fetch( '/polls/getDetails/' + location.pathname.substring( 7 ) ).then( response => {
|
||||
response.json().then( data => {
|
||||
this.votingDetails = data;
|
||||
this.hasLoadedBasics = true;
|
||||
} );
|
||||
} );
|
||||
this.votedOn = JSON.parse( localStorage.getItem( 'itemsVotedOn' ) ?? '{}' );
|
||||
},
|
||||
fingerprint() {
|
||||
// I am forced to do this because there are idiots in this world
|
||||
// https://stackoverflow.com/questions/27247806/generate-unique-id-for-each-device
|
||||
if ( !this.userIdentifier ) {
|
||||
const navigator_info = window.navigator;
|
||||
const screen_info = window.screen;
|
||||
let uid = navigator_info.mimeTypes.length;
|
||||
uid += navigator_info.userAgent.replace( /\D+/g, '' );
|
||||
uid += navigator_info.plugins.length;
|
||||
uid += screen_info.height || '';
|
||||
uid += screen_info.width || '';
|
||||
uid += screen_info.pixelDepth || '';
|
||||
this.userIdentifier = uid;
|
||||
}
|
||||
},
|
||||
save() {
|
||||
if ( this.newSuggestion.comment && this.newSuggestion.title ) {
|
||||
let alreadyExists = false;
|
||||
for ( let el in this.entries ) {
|
||||
if ( this.entries[ el ][ 'title' ].toLocaleLowerCase() === this.newSuggestion.title.toLocaleLowerCase() ) {
|
||||
alreadyExists = true;
|
||||
}
|
||||
}
|
||||
if ( !alreadyExists ) {
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( this.newSuggestion ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
},
|
||||
};
|
||||
fetch( '/polls/add/' + location.pathname.substring( 8 ), fetchOptions ).then( response => {
|
||||
if ( response.status === 418 ) {
|
||||
alert( 'One or more of the words in either your description or title is on our blocklist. Please make sure that you are not using any words that are NSFW, racist or similar.' );
|
||||
} else if ( response.status !== 200 ) {
|
||||
alert( 'there was an error updating' );
|
||||
} else {
|
||||
this.closePopup();
|
||||
this.getData();
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
alert( 'An entry with this name exists already. Please vote on that entry.' );
|
||||
}
|
||||
} else {
|
||||
alert( 'Not all required fields are filled out!' );
|
||||
}
|
||||
},
|
||||
vote( type, suggestionID ) {
|
||||
this.fingerprint();
|
||||
let voteType = type;
|
||||
let didDeactivate = false;
|
||||
if ( this.votedOn[ suggestionID ] === type ) {
|
||||
didDeactivate = true;
|
||||
if ( type === 'up' ) {
|
||||
voteType = 'down';
|
||||
} else {
|
||||
voteType = 'up';
|
||||
}
|
||||
} else if ( this.votedOn[ suggestionID ] ) {
|
||||
return;
|
||||
}
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'voteType': voteType, 'id': suggestionID, 'fingerprint': this.userIdentifier } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
},
|
||||
};
|
||||
fetch( '/polls/vote/' + location.pathname.substring( 7 ), fetchOptions ).then( response => {
|
||||
if ( response.status === 409 ) {
|
||||
alert( 'You have already voted on this!' );
|
||||
} else if ( response.status !== 200 ) {
|
||||
alert( 'there was an error updating' );
|
||||
} else {
|
||||
this.votedOn[ suggestionID ] = didDeactivate ? undefined : voteType;
|
||||
localStorage.setItem( 'itemsVotedOn', JSON.stringify( this.votedOn ) );
|
||||
this.getData();
|
||||
}
|
||||
} );
|
||||
},
|
||||
closePopup() {
|
||||
$( '#popup' ).fadeOut( 500 );
|
||||
$( 'body' ).removeClass( 'menuOpen' );
|
||||
this.getData();
|
||||
},
|
||||
addSuggestion () {
|
||||
$( '#popup' ).fadeIn( 500 );
|
||||
$( 'body' ).addClass( 'menuOpen' );
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getData();
|
||||
}
|
||||
} ).mount( '#app' );
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"pluginName": "Polls",
|
||||
"pluginDescription": "Create polls to ask the customers questions about your event!",
|
||||
"creator": "Janis Hutz",
|
||||
"maintainer": "Janis Hutz",
|
||||
"pluginWebsite": "https://libreevent.janishutz.com/plugins/polls",
|
||||
"pluginDocs": "https://libreevent.janishutz.com/docs/plugins/polls",
|
||||
"gitURL": "https://github.com/simplePCBuilding/libreevent/tree/master/src/server/backend/plugins/others/poll",
|
||||
"settingsURL": "/admin/plugins/polls",
|
||||
"mainPluginURL": "/voting",
|
||||
"logo": "",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* libreevent - pollRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
|
||||
// Memory caching system to prevent downtime
|
||||
// This is basically the same system as can be found in the JSON db's saving system
|
||||
let votingMemCache = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/voting.json' ) ) );
|
||||
let hasToSave = false;
|
||||
let isReadyToSave = true;
|
||||
|
||||
const saveVotingData = () => {
|
||||
if ( isReadyToSave ) {
|
||||
isReadyToSave = false;
|
||||
hasToSave = false;
|
||||
runSave();
|
||||
} else {
|
||||
hasToSave = true;
|
||||
}
|
||||
};
|
||||
|
||||
const runSave = () => {
|
||||
fs.writeFile( path.join( __dirname + '/data/voting.json' ), JSON.stringify( votingMemCache ), ( err ) => {
|
||||
if ( err ) {
|
||||
console.error( err );
|
||||
} else {
|
||||
isReadyToSave = true;
|
||||
if ( hasToSave ) {
|
||||
runSave();
|
||||
}
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
// There are idiots in this world so we have to filter
|
||||
// Filter list is from here: https://github.com/BurntRouter/filtered-word-lists plus some extra words
|
||||
let uidCrossReference = JSON.parse( fs.readFileSync( path.join( __dirname + '/data/voteUIDReferencing.json' ) ) );
|
||||
const fl = '' + fs.readFileSync( path.join( __dirname + '/data/filterlist.txt' ) );
|
||||
let votingDirections = { '1': 'up', '-1': 'down' };
|
||||
let filterList = [];
|
||||
let prevStart = 0;
|
||||
for ( let e in fl ) {
|
||||
if ( fl[ e ] === '\n' ) {
|
||||
filterList.push( fl.slice( prevStart, e ).toLocaleLowerCase() );
|
||||
prevStart = parseInt( e ) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const filtering = ( string ) => {
|
||||
// using these filter lists: https://github.com/BurntRouter/filtered-word-lists plus some extra words
|
||||
// returns true if text is ok
|
||||
const str = string.toLocaleLowerCase();
|
||||
for ( let badWord in filterList ) {
|
||||
if ( str.includes( filterList[ badWord ] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = ( app ) => {
|
||||
app.get( '/polls/:vote', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/html/voting.html' ) );
|
||||
} );
|
||||
|
||||
app.get( '/polls/css/:file', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/css/' + req.params.file ) );
|
||||
} );
|
||||
|
||||
app.get( '/polls/js/:file', ( req, res ) => {
|
||||
res.sendFile( path.join( __dirname + '/js/' + req.params.file ) );
|
||||
} );
|
||||
|
||||
app.get( '/polls/getDetails/:vote', ( req, res ) => {
|
||||
fs.readFile( path.join( __dirname + '/data/votingSettings.json' ), ( error, filedata ) => {
|
||||
res.send( JSON.parse( filedata )[ req.params.vote ] ?? {} );
|
||||
} );
|
||||
} );
|
||||
|
||||
app.post( '/polls/getVotedOn/:vote', ( req, res ) => {
|
||||
res.send( uidCrossReference[ req.body.fingerprint ] ?? {} );
|
||||
} );
|
||||
|
||||
app.get( '/polls/get/:vote', ( req, res ) => {
|
||||
res.send( votingMemCache[ req.params.vote ] );
|
||||
} );
|
||||
|
||||
app.post( '/polls/vote/:vote/', bodyParser.json(), ( req, res ) => {
|
||||
// up / down-voting
|
||||
if ( req.body.fingerprint != '' ) {
|
||||
if ( uidCrossReference[ req.body.fingerprint ] ) {
|
||||
if ( uidCrossReference[ req.body.fingerprint ][ req.params.vote ] ) {
|
||||
if ( uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] !== 0 ) {
|
||||
const vote = uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ];
|
||||
if ( votingDirections[ vote ] === req.body.voteType ) {
|
||||
res.status( 409 ).send( 'alreadyVotedOn' );
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] = 0;
|
||||
}
|
||||
} else {
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ] = {};
|
||||
}
|
||||
} else {
|
||||
uidCrossReference[ req.body.fingerprint ] = {};
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ] = {};
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] = 0;
|
||||
}
|
||||
if ( votingMemCache[ req.params.vote ] ) {
|
||||
if ( votingMemCache[ req.params.vote ][ req.body.id ] ) {
|
||||
if ( req.body.voteType === 'up' ) {
|
||||
votingMemCache[ req.params.vote ][ req.body.id ].count += 1;
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] += 1;
|
||||
} else if ( req.body.voteType === 'down' ) {
|
||||
votingMemCache[ req.params.vote ][ req.body.id ].count -= 1;
|
||||
uidCrossReference[ req.body.fingerprint ][ req.params.vote ][ req.body.id ] -= 1;
|
||||
}
|
||||
saveVotingData();
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 404 ).send( 'No Entry on this vote with' );
|
||||
}
|
||||
} else {
|
||||
res.status( 404 ).send( 'No Vote with this ID' );
|
||||
}
|
||||
} else {
|
||||
res.status( 400 ).send( 'Wrong request' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/polls/add/:vote', bodyParser.json(), ( req, res ) => {
|
||||
let data = req.body;
|
||||
if ( data.title && data.comment ) {
|
||||
if ( filtering( data.title ) && filtering( data.comment ) ) {
|
||||
if ( !votingMemCache[ req.params.vote ] ) {
|
||||
votingMemCache[ req.params.vote ] = {};
|
||||
}
|
||||
const id = parseInt( Object.keys( votingMemCache[ req.params.vote ] )[ Object.keys( votingMemCache[ req.params.vote ] ).length - 1 ] ?? 0 ) + 1;
|
||||
votingMemCache[ req.params.vote ][ id ] = data;
|
||||
votingMemCache[ req.params.vote ][ id ][ 'id' ] = id;
|
||||
votingMemCache[ req.params.vote ][ id ][ 'count' ] = 1;
|
||||
saveVotingData();
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 418 ).send( 'seriously?' );
|
||||
}
|
||||
} else {
|
||||
res.status( 400 ).send( 'incomplete' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/admin/plugins/polls', ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
res.sendFile( path.join( __dirname + '/html/settings.html' ) );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/admin/plugins/polls/getData', ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
res.sendFile( path.join( __dirname + '/data/votingSettings.json' ) );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/admin/plugins/polls/save', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.loggedInAdmin ) {
|
||||
fs.writeFileSync( path.join( __dirname + '/data/votingSettings.json' ), JSON.stringify( req.body ) );
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
# Creating new payment provider integrations
|
||||
All payment gateway integration plugins have to be in their own folders. This folder has to contain a file called [Payment gateway name as camelCase word (starting in small letter)]Routes.js, a file called plugin.json and any number of other files that it needs.
|
||||
|
||||
You will also need to add documentation for the user to set up the payment gateway. Read on below to find out how.
|
||||
|
||||
## Setting up the routes.js file
|
||||
Take some inspiration of the stripe or payrexx setup as these are officially supported by the system and have been developed by the original creator.
|
||||
|
||||
The express.js routes it has to expose are the following:
|
||||
|
||||
- /payments/prepare (POST) (returns object: { 'link': [Link to the payment gateway payment], 'id': [Purchase ID to subscribe to status updating] })
|
||||
- /payments/getStatus (SSE) (sends updates to the UI once subscribed (like payment success) using the id sent by /payments/prepare)
|
||||
|
||||
It can contain any number of (not interfering) routes. Please always use the /payments/ route as a base to avoid running into problems.
|
||||
|
||||
## configOption.json
|
||||
This file contains the settings that should be available in the settings page of libreevent. It should contain the following fields, as required by the settings.vue module.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"APISecret": "",
|
||||
"instance": ""
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"APIKey": {
|
||||
"display": "API key",
|
||||
"id": "APIKey",
|
||||
"tooltip":"This is the secret key API key you can get from the stripe dashboard. Please make a test purchase (FREE) before you go live!",
|
||||
"value": "",
|
||||
"type": "text"
|
||||
},
|
||||
"instance": {
|
||||
"display": "Instance",
|
||||
"id": "instance",
|
||||
"tooltip":"Please specify the instance name you used to create the payrexx account. (e.g. libreevent)",
|
||||
"value": "",
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* libreevent - module.payrexx.js
|
||||
*
|
||||
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const qs = require( 'qs' );
|
||||
const axios = require( 'axios' );
|
||||
const Base64 = require( 'crypto-js/enc-base64' );
|
||||
const hmacSHA256 = require( 'crypto-js/hmac-sha256' );
|
||||
const payrexxConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/config.payments.json' ) ) );
|
||||
|
||||
const baseUrl = 'https://api.payrexx.com/v1.0/';
|
||||
|
||||
if ( !payrexxConfig.APISecret || !payrexxConfig.instance ) {
|
||||
throw new Error( '[ Payrexx ] No API secret or instance name configured!' );
|
||||
}
|
||||
const instance = payrexxConfig.instance;
|
||||
const secret = payrexxConfig.APISecret;
|
||||
|
||||
exports.init = function () {
|
||||
function buildSignature ( query = '' ) {
|
||||
return Base64.stringify( hmacSHA256( query, secret ) );
|
||||
}
|
||||
|
||||
function buildBaseUrl ( path ) {
|
||||
return baseUrl + path + '?instance=' + instance;
|
||||
}
|
||||
|
||||
return {
|
||||
getGateway: function ( id ) {
|
||||
const baseUrl = buildBaseUrl( 'Gateway/' + id + '/' );
|
||||
const url = baseUrl + '&ApiSignature=' + encodeURIComponent( buildSignature() );
|
||||
return axios.get( url );
|
||||
},
|
||||
createGateway: function ( params ) {
|
||||
if ( !params.amount ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const defaultParams = {
|
||||
currency: 'CHF'
|
||||
};
|
||||
|
||||
let queryParams = Object.assign( {}, defaultParams, params );
|
||||
|
||||
const queryStr = qs.stringify( queryParams, { format: 'RFC1738' } );
|
||||
const signature = buildSignature( queryStr );
|
||||
|
||||
queryParams.ApiSignature = signature;
|
||||
const queryStrSigned = qs.stringify( queryParams );
|
||||
|
||||
const baseUrl = buildBaseUrl( 'Gateway/' );
|
||||
return axios.post( baseUrl, queryStrSigned );
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* libreevent - payrexxRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 08/12/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( '../../../db/db.js' );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
const ticket = require( '../../../tickets/ticketGenerator.js' );
|
||||
const payrexxModule = require( './module.payrexx.js' );
|
||||
const payrexx = payrexxModule.init();
|
||||
const TicketGenerator = new ticket();
|
||||
const generator = require( '../../../token.js' );
|
||||
|
||||
let sessionReference = {};
|
||||
let waitingClients = {};
|
||||
let pendingPayments = {};
|
||||
let gatewayReference = {};
|
||||
let paymentOk = {};
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.loggedInUser ) {
|
||||
db.getDataSimple( 'users', 'email', req.session.username ).then( user => {
|
||||
if ( user[ 0 ] ) {
|
||||
if ( user[ 0 ][ 'mail_confirmed' ] ) {
|
||||
let purchase = {
|
||||
'successRedirectUrl': settings.yourDomain + '/payments/success',
|
||||
'cancelRedirectUrl': settings.yourDomain + '/payments/canceled',
|
||||
'failedRedirectUrl': settings.yourDomain + '/payments/failed',
|
||||
'currency': settings.currency,
|
||||
'basket': [],
|
||||
'amount': 0,
|
||||
'referenceId': req.session.id,
|
||||
};
|
||||
|
||||
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[ 'basket' ].push( {
|
||||
'name': data[ event ][ item ].name,
|
||||
'quantity': data[ event ][ item ].count ?? 1,
|
||||
'amount': Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ),
|
||||
} );
|
||||
purchase[ 'amount' ] += Math.round( parseFloat( events[ event ][ 'categories' ][ data[ event ][ item ].category ].price[ data[ event ][ item ][ 'ticketOption' ] ] ) * 100 ) * ( data[ event ][ item ].count ?? 1 );
|
||||
}
|
||||
}
|
||||
const response = await payrexx.createGateway( purchase );
|
||||
if ( response.status === 200 ) {
|
||||
const session = response.data.data[ 0 ];
|
||||
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
|
||||
pendingPayments[ req.session.id ] = true;
|
||||
gatewayReference[ req.session.id ] = session.id;
|
||||
db.writeDataSimple( 'processingOrders', 'user_id', req.session.id, dat[ 0 ] ).then( () => {
|
||||
res.send( session.link );
|
||||
} );
|
||||
} else {
|
||||
res.status( 500 ).send( 'ERR_PAYMENT' );
|
||||
}
|
||||
} )();
|
||||
} );
|
||||
} 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( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
|
||||
}
|
||||
} else {
|
||||
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/payments/status', ( request, response ) => {
|
||||
response.writeHead( 200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
} );
|
||||
response.status( 200 );
|
||||
response.flushHeaders();
|
||||
response.write( 'data: connected\n\n' );
|
||||
waitingClients[ request.session.id ] = response;
|
||||
const ping = setInterval( () => {
|
||||
if ( !pendingPayments[ request.session.id ] ) {
|
||||
const stat = TicketGenerator.getGenerationStatus( request.session.id );
|
||||
if ( stat === 'done' ) {
|
||||
clearInterval( ping );
|
||||
setTimeout( () => {
|
||||
response.write( 'data: ready\n\n' );
|
||||
delete waitingClients[ request.session.id ];
|
||||
request.session.lastOrderID = request.session.id;
|
||||
request.session.id = generator.generateToken( 30 );
|
||||
response.end();
|
||||
}, 2000 );
|
||||
} else if ( stat === 'noTicket' ) {
|
||||
clearInterval( ping );
|
||||
response.write( 'data: noData\n\n' );
|
||||
response.end();
|
||||
delete waitingClients[ request.session.id ];
|
||||
}
|
||||
}
|
||||
}, 2000 );
|
||||
} );
|
||||
|
||||
app.get( '/user/2fa/ping', ( request, response ) => {
|
||||
if ( paymentOk[ request.session.id ] === 'ok' ) {
|
||||
delete paymentOk[ request.session.id ];
|
||||
response.send( { 'status': 'paymentOk' } );
|
||||
} else {
|
||||
if ( !pendingPayments[ request.session.id ] ) {
|
||||
const stat = TicketGenerator.getGenerationStatus( request.session.id );
|
||||
if ( stat === 'done' ) {
|
||||
request.session.lastOrderID = request.session.id;
|
||||
request.session.id = generator.generateToken( 30 );
|
||||
response.send( { 'status': 'ticketOk' } );
|
||||
} else if ( stat === 'noTicket' ) {
|
||||
response.send( { 'status': 'noTicket' } );
|
||||
} else {
|
||||
response.send( '' );
|
||||
}
|
||||
} else {
|
||||
response.send( '' );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/payments/webhook', bodyParser.json(), async ( req, res ) => {
|
||||
if ( !req.body ) {
|
||||
if ( !req.body.transaction ) {
|
||||
res.status( 400 ).send( 'ERR_REQ_WRONG' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ( req.body.transaction.status === 'confirmed' ) {
|
||||
const response = await payrexx.getGateway( gatewayReference[ req.body.transaction.referenceId ] );
|
||||
|
||||
if ( response.status === 200 ) {
|
||||
const gateway = response.data.data[ 0 ];
|
||||
|
||||
res.status( 200 ).end();
|
||||
if ( gateway.status === 'confirmed' ) {
|
||||
setTimeout( () => {
|
||||
if ( waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ] ) {
|
||||
waitingClients[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
|
||||
}
|
||||
}, 1000 );
|
||||
db.getDataSimple( 'processingOrders', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( dat => {
|
||||
db.getDataSimple( 'users', 'email', sessionReference[ response.data.data[ 0 ].id ][ 'email' ] ).then( user => {
|
||||
if ( user[ 0 ] && dat[ 0 ] ) {
|
||||
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[ response.data.data[ 0 ].id ][ 'tok' ] } ).then( () => {
|
||||
console.log( sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] );
|
||||
TicketGenerator.generateTickets( sessionReference[ response.data.data[ 0 ].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 ).then( () => {
|
||||
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( () => {
|
||||
db.deleteDataSimple( 'processingOrders', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).then( () => {
|
||||
delete pendingPayments[ sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ];
|
||||
} ).catch( error => {
|
||||
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
|
||||
} );
|
||||
} );
|
||||
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ response.data.data[ 0 ].id ][ 'tok' ] ).catch( error => {
|
||||
console.error( '[ PAYREXX ] ERROR whilst deleting data from DB: ' + error );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
TicketGenerator.sendErrorMail( sessionReference[ response.data.data[ 0 ].id ][ 'tok' ], sessionReference[ response.data.data[ 0 ].id ][ 'email' ] );
|
||||
}
|
||||
} );
|
||||
} ).catch( err => {
|
||||
console.error( err );
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
res.send( 'ok' );
|
||||
}
|
||||
} else {
|
||||
res.send( 'ok' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"APIKey": "",
|
||||
"endpointSecret": ""
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"APIKey": {
|
||||
"display": "API key",
|
||||
"id": "APIKey",
|
||||
"tooltip":"This is the secret key API key you can get from the stripe dashboard. Please make a test purchase (FREE) before you go live!",
|
||||
"value": "",
|
||||
"type": "text"
|
||||
},
|
||||
"endpointSecret": {
|
||||
"display": "Webhook endpoint secret",
|
||||
"id": "endpointSecret",
|
||||
"tooltip":"Please specify the endpoint secret that you can get from the stripe dashboard when creating the webhook integration",
|
||||
"value": "",
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* libreevent - stripeRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/25/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const db = require( '../../../db/db.js' );
|
||||
// TODO: update config files to non-secret version for final version
|
||||
const stripeConfig = JSON.parse( fs.readFileSync( path.join( __dirname + '/config.payments.json' ) ) );
|
||||
const stripe = require( 'stripe' )( stripeConfig[ 'APIKey' ] );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
const ticket = require( '../../../tickets/ticketGenerator.js' );
|
||||
const TicketGenerator = new ticket();
|
||||
const generator = require( '../../../token.js' );
|
||||
|
||||
const endpointSecret = stripeConfig[ 'endpointSecret' ];
|
||||
|
||||
let sessionReference = {};
|
||||
let waitingClients = {};
|
||||
let pendingPayments = {};
|
||||
let paymentOk = {};
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.post( '/payments/prepare', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.loggedInUser ) {
|
||||
db.getDataSimple( 'users', 'email', req.session.username ).then( user => {
|
||||
if ( user[ 0 ] ) {
|
||||
if ( user[ 0 ][ 'mail_confirmed' ] ) {
|
||||
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
|
||||
};
|
||||
|
||||
// Get cart and prepare order
|
||||
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': settings.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,
|
||||
} );
|
||||
}
|
||||
}
|
||||
const session = await stripe.checkout.sessions.create( purchase );
|
||||
sessionReference[ session.id ] = { 'tok': req.session.id, 'email': req.session.username };
|
||||
pendingPayments[ req.session.id ] = true;
|
||||
db.writeDataSimple( 'processingOrders', 'user_id', req.session.id, dat[ 0 ] ).then( () => {
|
||||
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( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
|
||||
}
|
||||
} else {
|
||||
res.status( 428 ).send( 'ERR_MAIL_UNCONFIRMED' );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).send( 'ERR_UNAUTHORIZED' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/payments/status', ( request, response ) => {
|
||||
response.writeHead( 200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
} );
|
||||
response.status( 200 );
|
||||
response.flushHeaders();
|
||||
response.write( 'data: connected\n\n' );
|
||||
waitingClients[ request.session.id ] = response;
|
||||
const ping = setInterval( () => {
|
||||
if ( !pendingPayments[ request.session.id ] ) {
|
||||
const stat = TicketGenerator.getGenerationStatus( request.session.id );
|
||||
if ( stat === 'done' ) {
|
||||
clearInterval( ping );
|
||||
setTimeout( () => {
|
||||
response.write( 'data: ready\n\n' );
|
||||
delete waitingClients[ request.session.id ];
|
||||
request.session.lastOrderID = request.session.id;
|
||||
request.session.id = generator.generateToken( 30 );
|
||||
response.end();
|
||||
}, 2000 );
|
||||
} else if ( stat === 'noTicket' ) {
|
||||
clearInterval( ping );
|
||||
response.write( 'data: noData\n\n' );
|
||||
response.end();
|
||||
delete waitingClients[ request.session.id ];
|
||||
}
|
||||
}
|
||||
}, 2000 );
|
||||
} );
|
||||
|
||||
app.get( '/user/2fa/ping', ( request, response ) => {
|
||||
if ( paymentOk[ request.session.id ] === 'ok' ) {
|
||||
delete paymentOk[ request.session.id ];
|
||||
response.send( { 'status': 'paymentOk' } );
|
||||
} else {
|
||||
if ( !pendingPayments[ request.session.id ] ) {
|
||||
const stat = TicketGenerator.getGenerationStatus( request.session.id );
|
||||
if ( stat === 'done' ) {
|
||||
request.session.lastOrderID = request.session.id;
|
||||
request.session.id = generator.generateToken( 30 );
|
||||
response.send( { 'status': 'ticketOk' } );
|
||||
} else if ( stat === 'noTicket' ) {
|
||||
response.send( { 'status': 'noTicket' } );
|
||||
} else {
|
||||
response.send( '' );
|
||||
}
|
||||
} else {
|
||||
response.send( '' );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/payments/webhook', bodyParser.raw( { type: 'application/json' } ), async ( req, res ) => {
|
||||
const payload = req.body;
|
||||
const sig = req.headers[ 'stripe-signature' ];
|
||||
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent( payload, sig, endpointSecret );
|
||||
} catch ( err ) {
|
||||
console.error( err );
|
||||
return res.status( 400 ).send( 'Webhook Error' );
|
||||
}
|
||||
|
||||
res.status( 200 ).end();
|
||||
if ( event.type === 'checkout.session.completed' ) {
|
||||
setTimeout( () => {
|
||||
if ( waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ] ) {
|
||||
waitingClients[ sessionReference[ event.data.object.id ][ 'tok' ] ].write( 'data: paymentOk\n\n' );
|
||||
}
|
||||
}, 1000 );
|
||||
db.getDataSimple( 'processingOrders', '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 ] && dat[ 0 ] ) {
|
||||
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 ).then( () => {
|
||||
db.deleteDataSimple( 'temp', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( () => {
|
||||
db.deleteDataSimple( 'processingOrders', 'user_id', sessionReference[ event.data.object.id ][ 'tok' ] ).then( () => {
|
||||
delete pendingPayments[ sessionReference[ event.data.object.id ][ 'tok' ] ];
|
||||
} ).catch( error => {
|
||||
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
console.error( '[ STRIPE ] ERROR whilst deleting data from DB: ' + error );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
console.error( '[ STRIPE ] ERROR whilst getting data from DB: ' + error );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
TicketGenerator.sendErrorMail( sessionReference[ event.data.object.id ][ 'tok' ], sessionReference[ event.data.object.id ][ 'email' ] );
|
||||
}
|
||||
} );
|
||||
} ).catch( err => {
|
||||
console.error( err );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"pluginName": "",
|
||||
"pluginDescription": "",
|
||||
"creator": "",
|
||||
"maintainer": "",
|
||||
"pluginWebsite": "",
|
||||
"pluginDocs": "",
|
||||
"gitURL": "",
|
||||
"settingsURL": "",
|
||||
"mainPluginURL": "",
|
||||
"logo": "",
|
||||
"version": ""
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* libreevent - pluginLoader.js
|
||||
*
|
||||
* Created by Janis Hutz 08/13/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
console.log( '\n\n[ Plugin Loader ] Loading plugins\n' );
|
||||
let otherPlugins = fs.readdirSync( path.join( __dirname + '/others' ) );
|
||||
for ( let plugin in otherPlugins ) {
|
||||
require( './others/' + otherPlugins[ plugin ] + '/' + otherPlugins[ plugin ] + 'Routes.js' )( app, settings );
|
||||
console.log( '[ Plugin Loader ] Loaded plugin "' + otherPlugins[ plugin ] + '"' );
|
||||
}
|
||||
|
||||
require( './payments/' + settings.payments + '/' + settings.payments + 'Routes.js' )( app, settings );
|
||||
console.log( '[ Plugin Loader ] Loaded ' + settings.payments + ' as payment gateway' );
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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 );
|
||||
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* libreevent - token.js
|
||||
*
|
||||
* Created by Janis Hutz 07/08/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
module.exports.generateToken = ( length ) => {
|
||||
let token = '';
|
||||
let min = 45;
|
||||
let max = 122;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
let randomNumber = Math.floor( Math.random() * ( max - min ) ) + min;
|
||||
while ( ( 58 < randomNumber && randomNumber < 63 ) || ( 90 < randomNumber && randomNumber < 95 ) || ( 95 < randomNumber && randomNumber < 97 ) ) {
|
||||
randomNumber = Math.floor( Math.random() * ( max - min ) ) + min;
|
||||
}
|
||||
token += String.fromCharCode( randomNumber );
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
module.exports.generateNumber = ( length ) => {
|
||||
let number = '';
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
number += Math.floor( Math.random() * 10 );
|
||||
}
|
||||
return number;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* libreevent - adminAPIRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const posth = require( './api/postHandler.js' );
|
||||
const geth = require( './api/getHandler.js' );
|
||||
const postHandler = new posth();
|
||||
const getHandler = new geth();
|
||||
const bodyParser = require( 'body-parser' );
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
// Add specific routes here to have them be checked first to not get general handling
|
||||
|
||||
app.get( '/getAPI/:call', ( req, res ) => {
|
||||
getHandler.handleCall( req.params.call, req.query, req.session, settings ).then( data => {
|
||||
if ( req.params.call === 'getReservedSeats' ) {
|
||||
let dat = data;
|
||||
dat[ 'reserved' ] = postHandler.getReservedSeats( req.query.event );
|
||||
res.send( dat );
|
||||
} else if ( req.params.call === 'getAllEvents' ) {
|
||||
let dat = data;
|
||||
const freeSeats = postHandler.getFreeSeatsCount();
|
||||
for ( let event in freeSeats ) {
|
||||
dat[ event ][ 'free' ] = freeSeats[ event ];
|
||||
}
|
||||
res.send( dat );
|
||||
} else if ( req.params.call === 'reloadData' ) {
|
||||
if ( req.session.loggedInAdmin || req.session.loggedInUser ) {
|
||||
postHandler.loadData();
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} else {
|
||||
res.send( data );
|
||||
}
|
||||
} ).catch( error => {
|
||||
res.status( error.code ?? 500 ).send( error.message );
|
||||
} );
|
||||
} );
|
||||
|
||||
app.post( '/API/:call', bodyParser.json(), ( req, res ) => {
|
||||
// add lang in the future
|
||||
postHandler.handleCall( req.params.call, req.body, req.session ).then( data => {
|
||||
res.send( data );
|
||||
} ).catch( error => {
|
||||
res.status( error.code ?? 500 ).send( error.message );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* libreevent - routes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/11/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const db = require( './db/db.js' );
|
||||
const pwdmanager = require( './credentials/pwdmanager.js' );
|
||||
const auth = require( './credentials/2fa.js' );
|
||||
const twoFA = new auth();
|
||||
const path = require( 'path' );
|
||||
const mail = require( './mail/mailSender.js' );
|
||||
const mailManager = new mail();
|
||||
const bodyParser = require( 'body-parser' );
|
||||
const generator = require( './token.js' );
|
||||
|
||||
let responseObjects = {};
|
||||
let authOk = {};
|
||||
let mailTokens = {};
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
app.get( '/user/details', ( request, response ) => {
|
||||
if ( request.session.loggedInUser ) {
|
||||
db.getDataSimple( 'users', 'email', request.session.username ).then( data => {
|
||||
if ( data[ 0 ] ) {
|
||||
let dat = {};
|
||||
for ( let element in data[ 0 ] ) {
|
||||
if ( element !== 'pass' ) {
|
||||
dat[ element ] = data[ 0 ][ element ];
|
||||
}
|
||||
}
|
||||
response.send( { 'data': dat, 'status': true } );
|
||||
} else {
|
||||
response.status( 404 ).send( { 'data': 'This user does not exist', 'status': false } );
|
||||
}
|
||||
} ).catch( () => {
|
||||
console.log( 'dbError' );
|
||||
response.status( 500 ).send( { 'data': 'There was an error reading data from the database. If this error persists, please contact the administrators', 'status': false } );
|
||||
} );
|
||||
} else {
|
||||
response.status( 403 ).sendFile( path.join( __dirname + '/../ui/en/errors/403.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
// app.get( '/test/user', ( req, res ) => {
|
||||
// req.session.loggedInUser = true;
|
||||
// req.session.username = 'info@janishutz.com';
|
||||
// res.send( 'ok' );
|
||||
// } );
|
||||
|
||||
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 => {
|
||||
if ( data.status ) {
|
||||
request.session.username = request.body.mail;
|
||||
if ( data.twoFA === 'simple' ) {
|
||||
( async () => {
|
||||
let tok = twoFA.registerStandardAuthentication()[ 'token' ];
|
||||
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
|
||||
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
|
||||
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( tok, ip, settings.yourDomain, settings.name ), 'Verify login', settings.mailSender );
|
||||
request.session.token = tok;
|
||||
response.send( { 'status': '2fa' } );
|
||||
} )();
|
||||
} else if ( data.twoFA === 'enhanced' ) {
|
||||
( async () => {
|
||||
let res = twoFA.registerEnhancedAuthentication();
|
||||
let ipRetrieved = request.headers[ 'x-forwarded-for' ];
|
||||
let ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : request.connection.remoteAddress;
|
||||
mailManager.sendMail( request.body.mail, await twoFA.generateTwoFAMail( res.token, ip, settings.yourDomain, settings.name ), 'Verify login', settings.mailSender );
|
||||
request.session.token = res.token;
|
||||
response.send( { 'status': '2fa+', 'code': res.code } );
|
||||
} )();
|
||||
} else {
|
||||
request.session.loggedInUser = true;
|
||||
response.send( { 'status': 'ok' } );
|
||||
}
|
||||
} else {
|
||||
response.send( { 'status': 'pwErr' } );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
response.status( 400 ).send( 'missingCredentials' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/user/2fa', ( request, response ) => {
|
||||
let tokType = twoFA.verifySimple( request.query.token );
|
||||
if ( tokType === 'standard' ) {
|
||||
request.session.loggedInUser = true;
|
||||
if ( responseObjects[ request.query.token ] ) {
|
||||
responseObjects[ request.query.token ].write( 'data: authenticated\n\n' );
|
||||
} else {
|
||||
authOk[ request.query.token ] = 'ok';
|
||||
}
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faSimple.html' ) );
|
||||
} else if ( tokType === 'enhanced' ) {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/2fa/2faEnhanced.html' ) );
|
||||
} else {
|
||||
response.status( 403 ).sendFile( path.join( __dirname + '/../ui/en/2fa/2faInvalid.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/user/2fa/verify', bodyParser.json(), ( request, response ) => {
|
||||
let verified = twoFA.verifyEnhanced( request.body.token, request.body.code );
|
||||
if ( verified ) {
|
||||
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';
|
||||
}
|
||||
response.send( 'ok' );
|
||||
} else response.status( 403 ).send( 'wrong' );
|
||||
} );
|
||||
|
||||
app.get( '/user/2fa/check', ( request, response ) => {
|
||||
response.writeHead( 200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
} );
|
||||
response.status( 200 );
|
||||
response.flushHeaders();
|
||||
response.write( 'data: connected\n\n' );
|
||||
responseObjects[ request.session.token ] = response;
|
||||
} );
|
||||
|
||||
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( '' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/user/logout', ( request, response ) => {
|
||||
request.session.loggedInUser = false;
|
||||
request.session.username = '';
|
||||
response.send( 'logoutOk' );
|
||||
} );
|
||||
|
||||
app.get( '/user/resendEmail', ( req, res ) => {
|
||||
if ( req.session.username ) {
|
||||
( async () => {
|
||||
let tok = generator.generateToken( 60 );
|
||||
mailTokens[ tok ] = req.session.username;
|
||||
mailManager.sendMail( req.session.username, await twoFA.generateSignupEmail( tok, settings.yourDomain, settings.name ), 'Confirm your email', settings.mailSender );
|
||||
} )();
|
||||
res.send( 'sent' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorised' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/user/signup', bodyParser.json(), ( request, response ) => {
|
||||
if ( request.body.password && request.body.password === request.body.password2 && request.body.firstName && request.body.name && request.body.country && request.body.mail ) {
|
||||
db.checkDataAvailability( 'users', 'email', request.body.mail ).then( status => {
|
||||
if ( status ) {
|
||||
response.send( 'exists' );
|
||||
} else {
|
||||
( async () => {
|
||||
let tok = generator.generateToken( 60 );
|
||||
mailTokens[ tok ] = request.body.mail;
|
||||
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 } ),
|
||||
'marketing': request.body.newsletter ?? null
|
||||
} ).then( () => {
|
||||
request.session.loggedInUser = true;
|
||||
request.session.username = request.body.mail;
|
||||
response.send( 'ok' );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
response.status( 400 ).send( 'incomplete' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/user/signup/confirm', ( request, response ) => {
|
||||
if ( Object.keys( mailTokens ).includes( request.query.token ) ) {
|
||||
request.session.username = mailTokens[ request.query.token ];
|
||||
db.writeDataSimple( 'users', 'email', request.session.username, { 'mail_confirmed': 'true' } );
|
||||
delete mailTokens[ request.query.token ];
|
||||
if ( settings.twoFA === 'allow' ) {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/signup/allowTwoFA.html' ) );
|
||||
} else if ( settings.twoFA === 'enforce' ) {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/signup/enforceTwoFA.html' ) );
|
||||
} else {
|
||||
response.sendFile( path.join( __dirname + '/../ui/en/signup/disallowTwoFA.html' ) );
|
||||
}
|
||||
} else {
|
||||
response.status( 400 ).sendFile( path.join( __dirname + '/../ui/en/signup/invalid.html' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/user/settings/:setting', bodyParser.json(), ( request, response ) => {
|
||||
let call = request.params.setting;
|
||||
if ( request.session.username ) {
|
||||
if ( call === '2fa' ) {
|
||||
db.writeDataSimple( 'users', 'email', request.session.username, { 'two_fa': request.body.twoFA } );
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.status( 404 ).send( 'Not found' );
|
||||
}
|
||||
} else {
|
||||
response.status( 403 ).send( 'unauthorised' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/user/settings', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.username ) {
|
||||
db.writeDataSimple( 'users', 'email', req.session.username, req.body );
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorised' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/settings/2fa', ( request, response ) => {
|
||||
response.send( settings.twoFA );
|
||||
} );
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"host": "",
|
||||
"database": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"port": 3306
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"host":"",
|
||||
"port": 587,
|
||||
"secure": false,
|
||||
"auth": {
|
||||
"user":"",
|
||||
"pass":""
|
||||
},
|
||||
"tls": {
|
||||
"servername": ""
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"init":false,
|
||||
"setupDone":false,
|
||||
"twoFA":"allow",
|
||||
"twoFAMode":"simple",
|
||||
"db":"mysql",
|
||||
"payments":"stripe",
|
||||
"name":"libreevent",
|
||||
"yourDomain":"",
|
||||
"mailSender":"",
|
||||
"maxTickets":10,
|
||||
"currency":"USD",
|
||||
"gcInterval":300,
|
||||
"ticketTimeout":900,
|
||||
"startPage":"default",
|
||||
"version":"1.0.1"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
2917
src/server/package-lock.json
generated
2917
src/server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,84 +0,0 @@
|
||||
{
|
||||
"name": "libreevent",
|
||||
"version": "1.0.7",
|
||||
"description": "Free & Open source event management solution",
|
||||
"main": "app.js",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"acorn": "^8.8.2",
|
||||
"buffer-from": "^1.1.2",
|
||||
"camel-case": "^4.1.2",
|
||||
"clean-css": "^5.3.2",
|
||||
"commander": "^9.5.0",
|
||||
"css-b64-images": "^0.2.5",
|
||||
"debug": "^4.3.4",
|
||||
"dot-case": "^3.0.4",
|
||||
"entities": "^4.4.0",
|
||||
"find-up": "^6.3.0",
|
||||
"html-minifier-terser": "^7.1.0",
|
||||
"jju": "^1.4.0",
|
||||
"locate-path": "^7.2.0",
|
||||
"lower-case": "^2.0.2",
|
||||
"minify": "^9.2.0",
|
||||
"ms": "^2.1.2",
|
||||
"no-case": "^3.0.4",
|
||||
"p-limit": "^4.0.0",
|
||||
"p-locate": "^6.0.0",
|
||||
"param-case": "^3.0.4",
|
||||
"pascal-case": "^3.1.2",
|
||||
"path-exists": "^5.0.0",
|
||||
"readjson": "^2.2.2",
|
||||
"relateurl": "^0.2.7",
|
||||
"response-time": "^2.3.2",
|
||||
"simport": "^1.2.0",
|
||||
"source-map": "^0.6.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"terser": "^5.16.5",
|
||||
"try-catch": "^3.0.1",
|
||||
"try-to-catch": "^3.0.1",
|
||||
"tslib": "^2.5.0",
|
||||
"yocto-queue": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pdfme/generator": "^1.2.6",
|
||||
"@seald-io/nedb": "^4.0.2",
|
||||
"axios": "^1.6.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"crypto-js": "^4.2.0",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.17.3",
|
||||
"html-to-text": "^9.0.5",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.9.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"png-to-ico": "^2.1.8",
|
||||
"qs": "^6.11.2",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"serve-static": "^1.15.0",
|
||||
"stripe": "^12.14.0",
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/simplePCBuilding/libreevent.git"
|
||||
},
|
||||
"keywords": [
|
||||
"event",
|
||||
"management",
|
||||
"solution"
|
||||
],
|
||||
"author": "Janis Hutz",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/simplePCBuilding/libreevent/issues"
|
||||
},
|
||||
"homepage": "https://libreevent.janishutz.com"
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
|
||||
const letters = [ ',', '{' ];
|
||||
|
||||
const writeJSONData = ( db, data ) => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
fs.writeFile( path.join( __dirname + '/data/' + db + '.json' ), JSON.stringify( data ), ( error ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
} else {
|
||||
resolve( true );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
const saveSettings = ( settings ) => {
|
||||
const settingsString = JSON.stringify( settings );
|
||||
let settingsToSave = '';
|
||||
for ( let letter in settingsString ) {
|
||||
if ( letters.includes( settingsString[ letter ] ) ) {
|
||||
settingsToSave += settingsString[ letter ] + '\n\t';
|
||||
} else if ( settingsString[ letter ] === '}' ) {
|
||||
settingsToSave += '\n' + settingsString[ letter ];
|
||||
} else {
|
||||
settingsToSave += settingsString[ letter ];
|
||||
}
|
||||
}
|
||||
fs.writeFileSync( path.join( __dirname + '/config/settings.config.json' ), settingsToSave );
|
||||
};
|
||||
|
||||
console.log( `
|
||||
|
||||
_ _ _ _
|
||||
| (_) | | |
|
||||
| |_| |__ _ __ ___ _____ _____ _ __ | |_
|
||||
| | | '_ \\| '__/ _ \\/ _ \\ \\ / / _ \\ '_ \\| __|
|
||||
| | | |_) | | | __/ __/\\ V / __/ | | | |_
|
||||
|_|_|_.__/|_| \\___|\\___| \\_/ \\___|_| |_|\\__|
|
||||
|
||||
|
||||
|
||||
|
||||
-------------------------------
|
||||
|
||||
==> Resetting DBs to prepare for build
|
||||
|
||||
` );
|
||||
|
||||
writeJSONData( 'booked', {} );
|
||||
writeJSONData( 'eventDrafts', {} );
|
||||
writeJSONData( 'events', {} );
|
||||
writeJSONData( 'locations', {} );
|
||||
writeJSONData( 'events', {} );
|
||||
writeJSONData( 'seatplan', {} );
|
||||
writeJSONData( 'tickets', {} );
|
||||
writeJSONData( 'rootAccount', {} );
|
||||
writeJSONData( 'db', {} );
|
||||
|
||||
saveSettings( {
|
||||
'init': false,
|
||||
'setupDone': false,
|
||||
'twoFA': 'allow',
|
||||
'twoFAMode': 'simple',
|
||||
'db': 'mysql',
|
||||
'payments': 'stripe',
|
||||
'name': 'libreevent',
|
||||
'yourDomain': '',
|
||||
'mailSender': '',
|
||||
'maxTickets': 10,
|
||||
'currency': 'USD',
|
||||
'gcInterval': 300,
|
||||
'ticketTimeout': 900,
|
||||
'startPage': 'default',
|
||||
'version': '1.0.1'
|
||||
} );
|
||||
|
||||
console.log( ' ==> Done!\n\n' );
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* libreevent - setupRoutes.js
|
||||
*
|
||||
* Created by Janis Hutz 07/18/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
let db = null;
|
||||
let pwm = null;
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const bodyParser = require( 'body-parser' );
|
||||
|
||||
|
||||
module.exports = ( app, settings ) => {
|
||||
let isSetupComplete = settings.setupDone;
|
||||
/*
|
||||
Setup start route that checks if setup key was correct
|
||||
*/
|
||||
|
||||
app.post( '/setup/start', bodyParser.json(), ( request, response ) => {
|
||||
if ( request.body.token === '' + fs.readFileSync( path.join( __dirname + '/../setupkey.txt' ) ) ) {
|
||||
request.session.setupKeyOk = true;
|
||||
response.send( 'ok' );
|
||||
} else {
|
||||
response.status( 400 ).send( 'incorrect' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/setup/getKeyStatus', ( req, res ) => {
|
||||
if ( req.session.setupKeyOk ) {
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/setup/saveBasicSettings', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.setupKeyOk ) {
|
||||
fs.writeFileSync( path.join( __dirname + '/../config/db.config.json' ), JSON.stringify( req.body.db ) );
|
||||
let emailSettings = {};
|
||||
emailSettings[ 'host' ] = req.body.email.host;
|
||||
emailSettings[ 'port' ] = req.body.email.port;
|
||||
emailSettings[ 'secure' ] = false;
|
||||
emailSettings[ 'auth' ] = { 'user': req.body.email.user, 'pass': req.body.email.pass };
|
||||
let hostSplit = req.body.email.host.split( '.' );
|
||||
emailSettings[ 'tls' ] = { 'servername': ( hostSplit[ hostSplit.length - 2 ] + '.' + hostSplit[ hostSplit.length - 1 ] ) };
|
||||
fs.writeFileSync( path.join( __dirname + '/../config/mail.config.json' ), JSON.stringify( emailSettings ) );
|
||||
if ( db === null ) {
|
||||
db = require( '../backend/db/db.js' );
|
||||
pwm = require( '../admin/pwdmanager.js' );
|
||||
}
|
||||
let updatedSettings = settings;
|
||||
updatedSettings[ 'name' ] = req.body.websiteName;
|
||||
updatedSettings[ 'yourDomain' ] = req.body.yourDomain;
|
||||
updatedSettings[ 'mailSender' ] = req.body.mailDisplay;
|
||||
db.saveSettings( updatedSettings );
|
||||
res.send( 'ok' );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.post( '/setup/saveRootAccount', bodyParser.json(), ( req, res ) => {
|
||||
if ( req.session.setupKeyOk ) {
|
||||
if ( db === null ) {
|
||||
db = require( '../backend/db/db.js' );
|
||||
pwm = require( '../admin/pwdmanager.js' );
|
||||
}
|
||||
pwm.hashPassword( req.body.password ).then( hash => {
|
||||
db.writeJSONData( 'rootAccount', { 'pass': hash, 'email': req.body.mail } );
|
||||
let updatedSettings = settings;
|
||||
updatedSettings[ 'setupDone' ] = true;
|
||||
updatedSettings[ 'init' ] = true;
|
||||
db.saveSettings( updatedSettings );
|
||||
isSetupComplete = true;
|
||||
( async () => {
|
||||
await db.initDB();
|
||||
db.reset();
|
||||
res.send( 'ok' );
|
||||
} )();
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).send( 'unauthorized' );
|
||||
}
|
||||
} );
|
||||
|
||||
app.get( '/getSetupStatus', ( req, res ) => {
|
||||
res.send( isSetupComplete );
|
||||
} );
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const db = require( './backend/db/db.js' );
|
||||
|
||||
( async () => {
|
||||
await db.initDB();
|
||||
db.reset();
|
||||
console.log( 'DONE' );
|
||||
} )();
|
||||
@@ -1 +0,0 @@
|
||||
klbjdfgsgsdjhlfkgfshjkgsfhjsiutuitpwergvuilavlsuv
|
||||
@@ -1,19 +0,0 @@
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let result = '';
|
||||
|
||||
// https://stackoverflow.com/questions/36129721/convert-number-to-alphabet-letter
|
||||
function printToLetter( number ) {
|
||||
let charIndex = number % alphabet.length;
|
||||
let quotient = number / alphabet.length;
|
||||
if ( charIndex - 1 === -1 ) {
|
||||
charIndex = alphabet.length;
|
||||
quotient --;
|
||||
}
|
||||
result = alphabet.charAt( charIndex - 1 ) + result;
|
||||
if ( quotient >= 1 ) {
|
||||
printToLetter( parseInt( quotient ) );
|
||||
}
|
||||
}
|
||||
|
||||
printToLetter( 150036 );
|
||||
console.log( result );
|
||||
@@ -1,132 +0,0 @@
|
||||
<!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 Invalid</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%;
|
||||
}
|
||||
|
||||
#code {
|
||||
padding: 0.75%;
|
||||
border: solid white 1px;
|
||||
border-radius: 7px;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
#popup {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5%;
|
||||
background-color: rgb(34, 34, 34);
|
||||
color: white;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
#popup::backdrop {
|
||||
background-color: rgba( 0, 0, 0, 0.8 );
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Two-Factor Authen­tication</h1>
|
||||
<p id="text">Please enter the code displayed on the login page down below to finish the Two-Factor Authentication.</p>
|
||||
<form onsubmit="return submitFunction()" id="form">
|
||||
<input type="text" name="code" id="code"><br>
|
||||
<input type="submit" value="Submit" class="submit">
|
||||
</form>
|
||||
<dialog id="popup">
|
||||
<p id="popup-message"></p>
|
||||
<form method="dialog">
|
||||
<input type="submit" value="Ok" class="submit">
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script>
|
||||
function submitFunction () {
|
||||
let code = document.getElementById( 'code' ).value;
|
||||
let data = '';
|
||||
if ( code.includes( ' ' ) ) {
|
||||
for ( let letter in code ) {
|
||||
if ( code[ letter ] != ' ' ) {
|
||||
data += code[ letter ];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = code;
|
||||
}
|
||||
if ( data.length == 6 ) {
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'code': data, 'token': location.search.substring( 7 ) } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( '/user/2fa/verify', fetchOptions ).then( res => {
|
||||
res.text().then( data => {
|
||||
if ( data === 'ok' ) {
|
||||
document.getElementById( 'text' ).innerText = 'Two-Factor Authentication is complete! Head back to the original page!';
|
||||
document.getElementById( 'form' ).innerHTML = '';
|
||||
openPopup( 'You have successfully authorised this login. You may now close this tab and head back to the original tab.' );
|
||||
} else {
|
||||
openPopup( 'This code you specified is invalid (or no longer valid). Please try again.' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
openPopup( 'Please enter a six-character code to proceed' );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function openPopup ( message ) {
|
||||
document.getElementById( 'popup-message' ).innerHTML = message;
|
||||
document.getElementById( 'popup' ).showModal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,132 +0,0 @@
|
||||
<!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 Invalid</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%;
|
||||
}
|
||||
|
||||
#code {
|
||||
padding: 0.75%;
|
||||
border: solid white 1px;
|
||||
border-radius: 7px;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
#popup {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5%;
|
||||
background-color: rgb(34, 34, 34);
|
||||
color: white;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
#popup::backdrop {
|
||||
background-color: rgba( 0, 0, 0, 0.8 );
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Two-Factor Authen­tication</h1>
|
||||
<p id="text">Please enter the code displayed on the login page down below to finish the Two-Factor Authentication.</p>
|
||||
<form onsubmit="return submitFunction()" id="form">
|
||||
<input type="text" name="code" id="code"><br>
|
||||
<input type="submit" value="Submit" class="submit">
|
||||
</form>
|
||||
<dialog id="popup">
|
||||
<p id="popup-message"></p>
|
||||
<form method="dialog">
|
||||
<input type="submit" value="Ok" class="submit">
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script>
|
||||
function submitFunction () {
|
||||
let code = document.getElementById( 'code' ).value;
|
||||
let data = '';
|
||||
if ( code.includes( ' ' ) ) {
|
||||
for ( let letter in code ) {
|
||||
if ( code[ letter ] != ' ' ) {
|
||||
data += code[ letter ];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = code;
|
||||
}
|
||||
if ( data.length == 6 ) {
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'code': data, 'token': location.search.substring( 7 ) } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( '/admin/2fa/verify', fetchOptions ).then( res => {
|
||||
res.text().then( data => {
|
||||
if ( data === 'ok' ) {
|
||||
document.getElementById( 'text' ).innerText = 'Two-Factor Authentication is complete! Head back to the original page!';
|
||||
document.getElementById( 'form' ).innerHTML = '';
|
||||
openPopup( 'You have successfully authorised this login. You may now close this tab and head back to the original tab.' );
|
||||
} else {
|
||||
openPopup( 'This code you specified is invalid (or no longer valid). Please try again.' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
} else {
|
||||
openPopup( 'Please enter a six-character code to proceed' );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function openPopup ( message ) {
|
||||
document.getElementById( 'popup-message' ).innerHTML = message;
|
||||
document.getElementById( 'popup' ).showModal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!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 Invalid</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%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Two-Factor Authen­tication Token invalid</h1>
|
||||
<p>The token you have specified is invalid. Please check that the link used is correct. If nothing helps, please try logging in again.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!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 Invalid</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%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Two-Factor Authen­tication Successful</h1>
|
||||
<p>Your two-factor authentication has been completed successfully. You were redirected automatically. You may now close this tab and return to the original browser tab.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>403</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>403</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
<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>
|
||||
@@ -1,57 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
<div class="content">
|
||||
<h1>Payment Failed</h1>
|
||||
<p>Your payment has failed! Please try again or use a different payment method</p>
|
||||
<button onclick="location.href = '/cart'" class="submit">Back to the cart</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!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%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 80%;
|
||||
height: 800px;
|
||||
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>There was an error whilst processing an order</h1>
|
||||
<p>This order will have to be processed manually!</p>
|
||||
<p>{{ order_id }}</p>
|
||||
<p>{{ email }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,67 +0,0 @@
|
||||
<!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%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 80%;
|
||||
height: 800px;
|
||||
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>There was an error whilst processing your order at {{ pageName }}</h1>
|
||||
<p>Your tickets will have to be processed manually and you will receive them as soon as they are ready.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,67 +0,0 @@
|
||||
<!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%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 80%;
|
||||
height: 800px;
|
||||
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>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email verification</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%;
|
||||
}
|
||||
|
||||
#code {
|
||||
padding: 0.75%;
|
||||
border: solid white 1px;
|
||||
border-radius: 7px;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
#popup {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5%;
|
||||
background-color: rgb(34, 34, 34);
|
||||
color: white;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
#popup::backdrop {
|
||||
background-color: rgba( 0, 0, 0, 0.8 );
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Email Verification</h1>
|
||||
<p id="text">We strongly encourage you to enable Two-Factor authentication for your account. Below you have the choice between not enabling it, enabling a mode where you just have to click the link in the email and you're in (simple) and a mode where you have to click the link in the mail and confirm the login by typing the code displayed on the main window (enhanced).</p>
|
||||
<form onsubmit="return submitFunction()" id="form">
|
||||
<select name="2fa" id="2fa">
|
||||
<option value="enhanced">Enhanced</option>
|
||||
<option value="simple">Simple</option>
|
||||
<option value="disabled">Disabled</option>
|
||||
</select><br>
|
||||
<input type="submit" value="Submit" class="submit">
|
||||
</form>
|
||||
<dialog id="popup">
|
||||
<p id="popup-message"></p>
|
||||
<form method="dialog">
|
||||
<input type="submit" value="Ok" class="submit">
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script>
|
||||
function submitFunction () {
|
||||
let mode = document.getElementById( '2fa' ).value;
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'twoFA': mode } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( '/user/settings/2fa', fetchOptions ).then( res => {
|
||||
res.text().then( data => {
|
||||
if ( data === 'ok' ) {
|
||||
document.getElementById( 'text' ).innerText = 'Your settings have been saved! You may change them at any point. You may now close this tab.';
|
||||
document.getElementById( 'form' ).innerHTML = '';
|
||||
openPopup( 'Your settings have been saved! You may change them at any point. You may now close this tab.' );
|
||||
} else {
|
||||
openPopup( 'An error occurred whilst saving your settings. Please try again. If it does not work, you can change this setting later at any point in the account page.' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
return false;
|
||||
}
|
||||
|
||||
function openPopup ( message ) {
|
||||
document.getElementById( 'popup-message' ).innerHTML = message;
|
||||
document.getElementById( 'popup' ).showModal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Verification Successful</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%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Email Verification Successful</h1>
|
||||
<p>Thank you! Your email address has been verified successfully. You may now close this tab.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,121 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email verification</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%;
|
||||
}
|
||||
|
||||
#code {
|
||||
padding: 0.75%;
|
||||
border: solid white 1px;
|
||||
border-radius: 7px;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
#popup {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5%;
|
||||
background-color: rgb(34, 34, 34);
|
||||
color: white;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
#popup::backdrop {
|
||||
background-color: rgba( 0, 0, 0, 0.8 );
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Email Verification</h1>
|
||||
<p id="text">This website requires you to use Two-Factor Authentication. Please choose your mode below. By default, the enhanced mode is enabled which requires you to type a 6-character code into a field after confirming the mail address. You can change this setting at any point later.</p>
|
||||
<form onsubmit="return submitFunction()" id="form">
|
||||
<select name="2fa" id="2fa">
|
||||
<option value="enhanced">Enhanced</option>
|
||||
<option value="simple">Simple</option>
|
||||
</select><br>
|
||||
<input type="submit" value="Submit" class="submit">
|
||||
</form>
|
||||
<dialog id="popup">
|
||||
<p id="popup-message"></p>
|
||||
<form method="dialog">
|
||||
<input type="submit" value="Ok" class="submit">
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script>
|
||||
function submitFunction () {
|
||||
let mode = document.getElementById( '2fa' ).value;
|
||||
let fetchOptions = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'twoFA': mode } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( '/user/settings/2fa', fetchOptions ).then( res => {
|
||||
res.text().then( data => {
|
||||
if ( data === 'ok' ) {
|
||||
document.getElementById( 'text' ).innerText = 'Your settings have been saved! You may change them at any point. You may now close this tab.';
|
||||
document.getElementById( 'form' ).innerHTML = '';
|
||||
openPopup( 'Your settings have been saved! You may change them at any point. You may now close this tab.' );
|
||||
} else {
|
||||
openPopup( 'An error occurred whilst saving your settings. Please try again. If it does not work, you can change this setting later at any point in the account page.' );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
return false;
|
||||
}
|
||||
|
||||
function openPopup ( message ) {
|
||||
document.getElementById( 'popup-message' ).innerHTML = message;
|
||||
document.getElementById( 'popup' ).showModal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Verification Token Invalid</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%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>Email Verification Token Invalid</h1>
|
||||
<p>The email verification token you specified is invalid. Please check that it is correct and try again. If it still doesn't work, please request a new one by logging into your <a href="/account">account</a> and clicking "resend email verification token"</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,70 +0,0 @@
|
||||
<!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>Password reset</h1>
|
||||
<p>Your new password is:</p>
|
||||
<p>{{ password }}</p>
|
||||
<p>Use it to sign into your account now and change it as soon as you have logged in.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!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>Welcome to {{ pageName }}</h1>
|
||||
<p>To complete your signup at {{ pageName }}, we need you to click the link below to confirm your email.</p>
|
||||
<a :href="host + '/user/signup/confirm?token=' + token" class="verify">Confirm</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user