mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-04-05 08:53:53 -04:00
Refactored server.js into separate controllers. Implemented UNIFI_TOKEN startup check within info.js. Implemented KIOSK_HOMEPAGE environment variable. Implemented new / redirect structure base on KIOSK_HOMEPAGE variable. Implemented Admin UI button within kiosk.ejs. Updated array.js with new deprecated variables. Updated docker-compose.yml. Updated README.md.
This commit is contained in:
247
controllers/api.js
Normal file
247
controllers/api.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const cache = require('../modules/cache');
|
||||
const unifi = require('../modules/unifi');
|
||||
const mail = require('../modules/mail');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const {updateCache} = require('../utils/cache');
|
||||
const types = require('../utils/types');
|
||||
const languages = require('../utils/languages');
|
||||
|
||||
module.exports = {
|
||||
api: {
|
||||
/**
|
||||
* GET - /api
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
endpoints: [
|
||||
{
|
||||
method: 'GET',
|
||||
endpoint: '/api'
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
endpoint: '/api/types'
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
endpoint: '/api/languages'
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
endpoint: '/api/vouchers'
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
endpoint: '/api/voucher'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
types: {
|
||||
/**
|
||||
* GET - /api/types
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
types: types(variables.voucherTypes)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
languages: {
|
||||
/**
|
||||
* GET - /api/languages
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
languages: Object.keys(languages).map(language => {
|
||||
return {
|
||||
code: language,
|
||||
name: languages[language]
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
vouchers: {
|
||||
/**
|
||||
* GET - /api/vouchers
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: async (req, res) => {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
vouchers: cache.vouchers.map((voucher) => {
|
||||
return {
|
||||
id: voucher.id,
|
||||
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
|
||||
type: !voucher.authorizedGuestLimit ? 'multi' : voucher.authorizedGuestLimit === 1 ? 'single' : 'multi',
|
||||
duration: voucher.timeLimitMinutes,
|
||||
data_limit: voucher.dataUsageLimitMBytes ? voucher.dataUsageLimitMBytes : null,
|
||||
download_limit: voucher.rxRateLimitKbps ? voucher.rxRateLimitKbps : null,
|
||||
upload_limit: voucher.txRateLimitKbps ? voucher.txRateLimitKbps : null
|
||||
};
|
||||
}),
|
||||
updated: cache.updated
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
voucher: {
|
||||
/**
|
||||
* POST - /api/voucher
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
// Verify valid body is sent
|
||||
if(!req.body || !req.body.type) {
|
||||
res.status(400).json({
|
||||
error: 'Invalid Body!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if email body is set
|
||||
if(req.body.email) {
|
||||
// Check if email module is enabled
|
||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||
res.status(400).json({
|
||||
error: 'Email Not Configured!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if email body is correct
|
||||
if(!req.body.email.language || !req.body.email.address) {
|
||||
res.status(400).json({
|
||||
error: 'Invalid Body!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if language is available
|
||||
if(!Object.keys(languages).includes(req.body.email.language)) {
|
||||
res.status(400).json({
|
||||
error: 'Unknown Language!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if type is implemented and valid
|
||||
const typeCheck = (variables.voucherTypes).split(';').includes(req.body.type);
|
||||
if(!typeCheck) {
|
||||
res.status(400).json({
|
||||
error: 'Unknown Type!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Create voucher code
|
||||
const voucherCode = await unifi.create(types(req.body.type, true), 1, `||;;||api||;;||local||;;||`).catch((e) => {
|
||||
res.status(500).json({
|
||||
error: e,
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
|
||||
// Update application cache
|
||||
await updateCache();
|
||||
|
||||
if(voucherCode) {
|
||||
// Locate voucher data within cache
|
||||
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
||||
if(!voucherData) {
|
||||
res.status(500).json({
|
||||
error: 'Invalid application cache!',
|
||||
data: {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should send and email
|
||||
if(req.body.email) {
|
||||
// Send mail
|
||||
const emailResult = await mail.send(req.body.email.address, voucherData, req.body.email.language).catch((e) => {
|
||||
res.status(500).json({
|
||||
error: e,
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
|
||||
// Verify is the email was sent successfully
|
||||
if(emailResult) {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
voucher: {
|
||||
id: voucherData.id,
|
||||
code: voucherCode
|
||||
},
|
||||
email: {
|
||||
status: 'SENT',
|
||||
address: req.body.email.address
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.json({
|
||||
error: null,
|
||||
data: {
|
||||
message: 'OK',
|
||||
voucher: {
|
||||
id: voucherData.id,
|
||||
code: voucherCode
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
85
controllers/authentication.js
Normal file
85
controllers/authentication.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const jwt = require('../modules/jwt');
|
||||
|
||||
module.exports = {
|
||||
login: {
|
||||
/**
|
||||
* GET - /login
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
// Check if authentication is disabled
|
||||
if (variables.authDisabled) {
|
||||
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
return;
|
||||
}
|
||||
|
||||
const hour = new Date().getHours();
|
||||
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
|
||||
res.render('login', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
app_header: timeHeader,
|
||||
internalAuth: variables.authInternalEnabled,
|
||||
oidcAuth: variables.authOidcEnabled
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /login
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
// Check if internal authentication is enabled
|
||||
if(!variables.authInternalEnabled) {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordCheck = req.body.password === variables.authInternalPassword;
|
||||
|
||||
if (!passwordCheck) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Password Invalid!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`);
|
||||
return;
|
||||
}
|
||||
|
||||
res.cookie('authorization', jwt.sign(), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
},
|
||||
|
||||
logout: {
|
||||
/**
|
||||
* GET - /logout
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
// Check if authentication is disabled
|
||||
if (variables.authDisabled) {
|
||||
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(req.oidc) {
|
||||
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/oidc/logout`);
|
||||
} else {
|
||||
res.cookie('authorization', '', {httpOnly: true, expires: new Date(0)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
111
controllers/bulk.js
Normal file
111
controllers/bulk.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const cache = require('../modules/cache');
|
||||
const print = require('../modules/print');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const notes = require('../utils/notes');
|
||||
const time = require('../utils/time');
|
||||
const bytes = require('../utils/bytes');
|
||||
const languages = require('../utils/languages');
|
||||
|
||||
module.exports = {
|
||||
print: {
|
||||
/**
|
||||
* GET - /bulk/print
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
if(variables.printers === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
res.render('components/bulk-print', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
timeConvert: time,
|
||||
bytesConvert: bytes,
|
||||
notesConvert: notes,
|
||||
languages,
|
||||
defaultLanguage: variables.translationDefault,
|
||||
printers: variables.printers.split(','),
|
||||
vouchers: cache.vouchers,
|
||||
updated: cache.updated
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /bulk/print
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
if(variables.printers === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!variables.printers.includes(req.body.printer)) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!req.body.vouchers) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'No selected vouchers to print!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Single checkboxes get send as string so conversion is needed
|
||||
if(typeof req.body.vouchers === 'string') {
|
||||
req.body.vouchers = [req.body.vouchers];
|
||||
}
|
||||
|
||||
const vouchers = req.body.vouchers.map((voucher) => {
|
||||
return cache.vouchers.find((e) => {
|
||||
return e.id === voucher;
|
||||
});
|
||||
});
|
||||
|
||||
if(!vouchers.includes(undefined)) {
|
||||
if(req.body.printer === 'pdf') {
|
||||
const buffers = await print.pdf(vouchers, req.body.language, true);
|
||||
const pdfData = Buffer.concat(buffers);
|
||||
res.writeHead(200, {
|
||||
'Content-Length': Buffer.byteLength(pdfData),
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment;filename=bulk_vouchers_${new Date().getTime()}.pdf`
|
||||
}).end(pdfData);
|
||||
} else {
|
||||
let printSuccess = true;
|
||||
|
||||
for(let voucher = 0; voucher < vouchers.length; voucher++) {
|
||||
const printResult = await print.escpos(vouchers[voucher], req.body.language, req.body.printer).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(!printResult) {
|
||||
printSuccess = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(printSuccess) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Vouchers send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
36
controllers/error.js
Normal file
36
controllers/error.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const log = require('../modules/log');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Handler for 404 status codes
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
404: (req, res) => {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for 500 status codes
|
||||
*
|
||||
* @param err
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*/
|
||||
500: (err, req, res, next) => {
|
||||
log.error(err.stack);
|
||||
res.status(500);
|
||||
res.render('500', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: err.stack
|
||||
});
|
||||
}
|
||||
};
|
||||
168
controllers/kiosk.js
Normal file
168
controllers/kiosk.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const log = require('../modules/log');
|
||||
const cache = require('../modules/cache');
|
||||
const unifi = require('../modules/unifi');
|
||||
const print = require('../modules/print');
|
||||
const mail = require('../modules/mail');
|
||||
const qr = require('../modules/qr');
|
||||
const translation = require('../modules/translation');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const types = require('../utils/types');
|
||||
const time = require('../utils/time');
|
||||
const bytes = require('../utils/bytes');
|
||||
const languages = require('../utils/languages');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* GET - /kiosk
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
// Check if kiosk is disabled
|
||||
if(!variables.kioskEnabled) {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
res.render('kiosk', {
|
||||
t: translation('kiosk', req.locale.language),
|
||||
languages,
|
||||
language: req.locale.language,
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
timeConvert: time,
|
||||
bytesConvert: bytes,
|
||||
voucher_types: types(variables.kioskVoucherTypes),
|
||||
kiosk_name_required: variables.kioskNameRequired,
|
||||
kiosk_homepage: variables.kioskHomepage
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /kiosk
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
// Check if kiosk is disabled
|
||||
if(!variables.kioskEnabled) {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need to generate a voucher or send an email with an existing voucher
|
||||
if(req.body && req.body.id && req.body.code && req.body.email) {
|
||||
// Check if email functions are enabled
|
||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get voucher from cache
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.body.id;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
const emailResult = await mail.send(req.body.email, voucher, req.locale.language).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
});
|
||||
|
||||
if(emailResult) {
|
||||
res.render('kiosk', {
|
||||
t: translation('kiosk', req.locale.language),
|
||||
languages,
|
||||
language: req.locale.language,
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||
unifiSsid: variables.unifiSsid,
|
||||
unifiSsidPassword: variables.unifiSsidPassword,
|
||||
qr: await qr(),
|
||||
voucherId: req.body.id,
|
||||
voucherCode: req.body.code,
|
||||
email: req.body.email
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const typeCheck = (variables.kioskVoucherTypes).split(';').includes(req.body['voucher-type']);
|
||||
|
||||
if (!typeCheck) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(variables.kioskNameRequired && req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
return;
|
||||
}
|
||||
|
||||
const voucherNote = `${variables.kioskNameRequired ? req.body['voucher-note'] : ''}||;;||kiosk||;;||local||;;||`;
|
||||
|
||||
// Create voucher code
|
||||
const voucherCode = await unifi.create(types(req.body['voucher-type'], true), 1, voucherNote).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
});
|
||||
|
||||
if (voucherCode) {
|
||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||
|
||||
const vouchers = await unifi.list().catch((e) => {
|
||||
log.error('[Cache] Error requesting vouchers!');
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
});
|
||||
|
||||
if (vouchers) {
|
||||
cache.vouchers = vouchers;
|
||||
cache.updated = new Date().getTime();
|
||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||
|
||||
// Locate voucher data within cache
|
||||
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
||||
if(!voucherData) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid application cache!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto print voucher if enabled
|
||||
await print.escpos(voucherData, req.locale.language, variables.kioskPrinter).catch((e) => {
|
||||
log.error(`[Kiosk] Unable to auto-print voucher on printer: ${variables.kioskPrinter}!`);
|
||||
log.error(e);
|
||||
});
|
||||
|
||||
res.render('kiosk', {
|
||||
t: translation('kiosk', req.locale.language),
|
||||
languages,
|
||||
language: req.locale.language,
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||
unifiSsid: variables.unifiSsid,
|
||||
unifiSsidPassword: variables.unifiSsidPassword,
|
||||
qr: await qr(),
|
||||
voucherId: voucherData.id,
|
||||
voucherCode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
37
controllers/status.js
Normal file
37
controllers/status.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const status = require('../utils/status');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* GET - /status
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: async (req, res) => {
|
||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
||||
|
||||
res.render('status', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
gitTag: variables.gitTag,
|
||||
gitBuild: variables.gitBuild,
|
||||
kioskEnabled: variables.kioskEnabled,
|
||||
user: user,
|
||||
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
||||
authDisabled: variables.authDisabled,
|
||||
status: status()
|
||||
});
|
||||
}
|
||||
};
|
||||
291
controllers/voucher.js
Normal file
291
controllers/voucher.js
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const log = require('../modules/log');
|
||||
const cache = require('../modules/cache');
|
||||
const unifi = require('../modules/unifi');
|
||||
const print = require('../modules/print');
|
||||
const mail = require('../modules/mail');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const types = require('../utils/types');
|
||||
const notes = require('../utils/notes');
|
||||
const time = require('../utils/time');
|
||||
const bytes = require('../utils/bytes');
|
||||
const languages = require('../utils/languages');
|
||||
|
||||
module.exports = {
|
||||
voucher: {
|
||||
/**
|
||||
* GET - /voucher/:id
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.params.id;
|
||||
});
|
||||
const guests = cache.guests.filter((e) => {
|
||||
return e.voucher_code === voucher.code;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
res.render('components/details', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
timeConvert: time,
|
||||
bytesConvert: bytes,
|
||||
notesConvert: notes,
|
||||
voucher,
|
||||
guests,
|
||||
updated: cache.updated
|
||||
});
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /voucher
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
if (typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if(req.body['voucher-type'] !== 'custom') {
|
||||
const typeCheck = (variables.voucherTypes).split(';').includes(req.body['voucher-type']);
|
||||
|
||||
if (!typeCheck) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: null };
|
||||
const voucherNote = `${req.body['voucher-note'] !== '' ? req.body['voucher-note'] : ''}||;;||web||;;||${req.oidc ? 'oidc' : 'local'}||;;||${req.oidc ? user.email.split('@')[1].toLowerCase() : ''}`;
|
||||
|
||||
// Create voucher code
|
||||
const voucherCode = await unifi.create(types(req.body['voucher-type'] === 'custom' ? `${req.body['voucher-duration-type'] === 'day' ? (parseInt(req.body['voucher-duration']) * 24 * 60) : req.body['voucher-duration-type'] === 'hour' ? (parseInt(req.body['voucher-duration']) * 60) : parseInt(req.body['voucher-duration'])},${req.body['voucher-usage'] === '-1' ? req.body['voucher-quota'] : req.body['voucher-usage']},${req.body['voucher-upload-limit']},${req.body['voucher-download-limit']},${req.body['voucher-data-limit']};` : req.body['voucher-type'], true), parseInt(req.body['voucher-amount']), voucherNote).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(voucherCode) {
|
||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||
|
||||
const vouchers = await unifi.list().catch((e) => {
|
||||
log.error('[Cache] Error requesting vouchers!');
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(vouchers) {
|
||||
cache.vouchers = vouchers;
|
||||
cache.updated = new Date().getTime();
|
||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: parseInt(req.body['voucher-amount']) > 1 ? `${req.body['voucher-amount']} Vouchers Created!` : `Voucher Created: ${voucherCode}`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove: {
|
||||
/**
|
||||
* GET - /voucher/:id/remove
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: async (req, res) => {
|
||||
// Revoke voucher code
|
||||
const response = await unifi.remove(req.params.id).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(response) {
|
||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||
|
||||
const vouchers = await unifi.list().catch((e) => {
|
||||
log.error('[Cache] Error requesting vouchers!');
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(vouchers) {
|
||||
cache.vouchers = vouchers;
|
||||
cache.updated = new Date().getTime();
|
||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher Removed!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
print: {
|
||||
/**
|
||||
* GET - /voucher/:id/print
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
if(variables.printers === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.params.id;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
res.render('components/print', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
languages,
|
||||
defaultLanguage: variables.translationDefault,
|
||||
printers: variables.printers.split(','),
|
||||
voucher,
|
||||
updated: cache.updated
|
||||
});
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /voucher/:id/print
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
if(variables.printers === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!variables.printers.includes(req.body.printer)) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.params.id;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
if(req.body.printer === 'pdf') {
|
||||
const buffers = await print.pdf(voucher, req.body.language);
|
||||
const pdfData = Buffer.concat(buffers);
|
||||
res.writeHead(200, {
|
||||
'Content-Length': Buffer.byteLength(pdfData),
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment;filename=voucher_${req.params.id}.pdf`
|
||||
}).end(pdfData);
|
||||
} else {
|
||||
const printResult = await print.escpos(voucher, req.body.language, req.body.printer).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(printResult) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
email: {
|
||||
/**
|
||||
* GET - /voucher/:id/email
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: (req, res) => {
|
||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.params.id;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
res.render('components/email', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
languages,
|
||||
defaultLanguage: variables.translationDefault,
|
||||
voucher,
|
||||
updated: cache.updated
|
||||
});
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* POST - /voucher/:id/email
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
post: async (req, res) => {
|
||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const voucher = cache.vouchers.find((e) => {
|
||||
return e.id === req.params.id;
|
||||
});
|
||||
|
||||
if(voucher) {
|
||||
const emailResult = await mail.send(req.body.email, voucher, req.body.language).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(emailResult) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Email has been sent!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
} else {
|
||||
res.status(404);
|
||||
res.render('404', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
142
controllers/vouchers.js
Normal file
142
controllers/vouchers.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Import own modules
|
||||
*/
|
||||
const variables = require('../modules/variables');
|
||||
const log = require('../modules/log');
|
||||
const cache = require('../modules/cache');
|
||||
const unifi = require('../modules/unifi');
|
||||
|
||||
/**
|
||||
* Import own utils
|
||||
*/
|
||||
const types = require('../utils/types');
|
||||
const notes = require('../utils/notes');
|
||||
const time = require('../utils/time');
|
||||
const bytes = require('../utils/bytes');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* GET - /vouchers
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
get: async (req, res) => {
|
||||
if(req.query.refresh) {
|
||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||
|
||||
const vouchers = await unifi.list().catch((e) => {
|
||||
log.error('[Cache] Error requesting vouchers!');
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(!vouchers) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info('[Cache] Requesting UniFi Guests...');
|
||||
|
||||
const guests = await unifi.guests().catch((e) => {
|
||||
log.error('[Cache] Error requesting guests!');
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
});
|
||||
|
||||
if(vouchers && guests) {
|
||||
cache.vouchers = vouchers;
|
||||
cache.guests = guests;
|
||||
cache.updated = new Date().getTime();
|
||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||
log.info(`[Cache] Saved ${guests.length} guest(s)`);
|
||||
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Synced Vouchers & Guests!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
||||
|
||||
res.render('voucher', {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
gitTag: variables.gitTag,
|
||||
gitBuild: variables.gitBuild,
|
||||
user: user,
|
||||
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
||||
authDisabled: variables.authDisabled,
|
||||
info: req.flashMessage.type === 'info',
|
||||
info_text: req.flashMessage.message || '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
kioskEnabled: variables.kioskEnabled,
|
||||
timeConvert: time,
|
||||
bytesConvert: bytes,
|
||||
notesConvert: notes,
|
||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||
printer_enabled: variables.printers !== '',
|
||||
voucher_types: types(variables.voucherTypes),
|
||||
voucher_custom: variables.voucherCustom,
|
||||
vouchers: cache.vouchers.filter((item) => {
|
||||
if(variables.authOidcRestrictVisibility && req.oidc) {
|
||||
return item.name && notes(item.name).auth_oidc_domain === user.email.split('@')[1].toLowerCase();
|
||||
}
|
||||
|
||||
return true;
|
||||
}).filter((item) => {
|
||||
if(req.query.status === 'available') {
|
||||
return item.authorizedGuestCount === 0 && !item.expired;
|
||||
}
|
||||
|
||||
if(req.query.status === 'in-use') {
|
||||
return item.authorizedGuestCount > 0 && !item.expired;
|
||||
}
|
||||
|
||||
if(req.query.status === 'expired') {
|
||||
return item.expired;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).filter((item) => {
|
||||
if(req.query.quota === 'multi-use') {
|
||||
return (item.authorizedGuestLimit && item.authorizedGuestLimit > 1) || !item.authorizedGuestLimit;
|
||||
}
|
||||
|
||||
if(req.query.quota === 'single-use') {
|
||||
return item.authorizedGuestLimit && item.authorizedGuestLimit === 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).sort((a, b) => {
|
||||
if(req.query.sort === 'code') {
|
||||
if (a.code > b.code) return -1;
|
||||
if (a.code < b.code) return 1;
|
||||
}
|
||||
|
||||
if(req.query.sort === 'note') {
|
||||
if ((notes(a.name).note || '') > (notes(b.name).note || '')) return -1;
|
||||
if ((notes(a.name).note || '') < (notes(b.name).note || '')) return 1;
|
||||
}
|
||||
|
||||
if(req.query.sort === 'duration') {
|
||||
if (a.timeLimitMinutes > b.timeLimitMinutes) return -1;
|
||||
if (a.timeLimitMinutes < b.timeLimitMinutes) return 1;
|
||||
}
|
||||
|
||||
if(req.query.sort === 'status') {
|
||||
if (a.authorizedGuestCount > b.authorizedGuestCount) return -1;
|
||||
if (a.authorizedGuestCount < b.authorizedGuestCount) return 1;
|
||||
}
|
||||
}),
|
||||
updated: cache.updated,
|
||||
filters: {
|
||||
status: req.query.status,
|
||||
quota: req.query.quota
|
||||
},
|
||||
sort: req.query.sort
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user