642 lines
23 KiB
JavaScript
642 lines
23 KiB
JavaScript
const express = require('express');
|
||
const bodyParser = require('body-parser');
|
||
const session = require('express-session');
|
||
const QRCode=require('qrcode');
|
||
const crypto=require('crypto');
|
||
const path=require('path');
|
||
const fs = require('fs');
|
||
require('dotenv').config();
|
||
const port=process.env.PORT||3000;
|
||
const base_url = process.env.BASE_URL;
|
||
const stripe=require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||
|
||
const app = express();
|
||
app.set('view engine','ejs');
|
||
app.use(express.json());
|
||
app.use(express.static('public'));
|
||
const PORT = 3000;
|
||
const MainURL ="http://localhost:3000";
|
||
const PWSalt ="!SaltyMagic7283715374";
|
||
const EmailSalt="!SaltyMagic3562196239";
|
||
const QRSalt ="!SaltyMagic5392370662";
|
||
|
||
//
|
||
// ToDo, Actions:
|
||
// + Login with Password
|
||
// + Login with Token
|
||
// + Email Link
|
||
// + Display Many Tickets (User)
|
||
// + Display One Ticket (User)
|
||
// + Camp Editor
|
||
// + Use Ticket
|
||
// + Claim Any Tickets
|
||
// + Purge Revoked Function (Admin)
|
||
// + Issue Tickets from Camp Admin Page
|
||
// + Send email when new tickets are issued/offered
|
||
// + Make one Update button on editcamp (Admin)
|
||
// + Make one Update button on manytickets (User)
|
||
// + Turn ticket use on/off from Settings (Admin)
|
||
// + Turn email on/off from Settings (Admin)
|
||
// + Magic-link Login System
|
||
// Create Account (User)
|
||
// Change Password (User)
|
||
// Deactivate individual magic links (User)
|
||
// Deactivate individual magic links (Admin)
|
||
// See how much each camp/ticket has paid (Admin)
|
||
//
|
||
// ToDo, Later
|
||
// + Use a templating engine
|
||
// + Store password hashed and salted
|
||
// Make all HTML look nice
|
||
// Logging and Replay system(?)
|
||
// Stripe Integration
|
||
// More efficent data structure: TicketsByCamp, TicketsByOffered, TicketsByOwner
|
||
//
|
||
|
||
//
|
||
// Login workflow:
|
||
// There is always an implicit magic link that allows anyone to log in with their email address and hash(GlobalSalt+PerUserSalty+Email), except if the user sets a password for himself.
|
||
// We store a per-user salt, initially the empty string, in case we need to invalidate particular users' magic links.
|
||
//
|
||
|
||
//
|
||
// Commands
|
||
// ISSUE campname email
|
||
// USE ticket
|
||
// STATUS ticket i/u/r
|
||
// CLAIM ticket email
|
||
//
|
||
|
||
//
|
||
// Stripe Integration:
|
||
// The Stripe CLI is configured for Andrew Tepper with account id acct_1QZlMSCHHjDgpHos
|
||
// Login on stripe.com is tickets@fallsonfire.net Password is x65195241X.1
|
||
//
|
||
|
||
function base64ToBase64Url(base64) {
|
||
return base64
|
||
.replace(/\+/g, '-') // Replace '+' with '-'
|
||
.replace(/\//g, '_') // Replace '/' with '_'
|
||
.replace(/=+$/, ''); // Remove trailing '='
|
||
}
|
||
|
||
function hashEmail(email) {
|
||
const hash0=crypto.createHash('sha256');
|
||
const usersalt=email in users ? (users[email].linksalt ? users[email].linksalt : "") : "";
|
||
const hash1=hash0.update(email+EmailSalt+usersalt);
|
||
const hash=hash1.digest("base64");
|
||
return base64ToBase64Url(hash);
|
||
}
|
||
|
||
function hashPW(pw) {
|
||
const hash0=crypto.createHash('sha256');
|
||
const hash1=hash0.update(pw+PWSalt);
|
||
const hash=hash1.digest("base64");
|
||
return(hash);
|
||
}
|
||
|
||
function hashQR(t,ownername) {
|
||
const hash0=crypto.createHash('sha256');
|
||
const hash1=hash0.update(t+QRSalt+ownername);
|
||
const hash=base64ToBase64Url(hash1.digest("base64")).slice(0,6);
|
||
return(hash);
|
||
}
|
||
|
||
function GetMagicLink(email) {
|
||
return MainURL+"/login?u="+email+"&h="+hashEmail(email);
|
||
}
|
||
|
||
function MagicLinkValid(email,hash) {
|
||
if (HasPW(email)) return false;
|
||
return hash==hashEmail(email);
|
||
}
|
||
|
||
|
||
//
|
||
// In-memory data structures
|
||
//
|
||
// There are two ways to log in: with a username/password or with a token.
|
||
//
|
||
//let users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" },
|
||
// "fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false, linksalt:"boobsarefun" }
|
||
// };
|
||
//
|
||
// Status can be "i"=issued, "u"=used, "r"=revoked"
|
||
// const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"i" },
|
||
// "habitat-2" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"u" },
|
||
// "habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppy@egenesis.com" , paid: 0.00, status:"i" },
|
||
// "habitat-4" : { owner: "teppy@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00, status:"i" },
|
||
// "habitat-5" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"r" },
|
||
// "habitat-6" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"i" },
|
||
// };
|
||
// const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
||
|
||
let users={};
|
||
let tickets={};
|
||
let camps={};
|
||
let settings={};
|
||
|
||
function InitDatabase() {
|
||
for (const key in users ) delete users[key];
|
||
for (const key in tickets) delete tickets[key];
|
||
for (const key in camps ) delete camps[key];
|
||
users["teppy@egenesis.com" ]= { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" };
|
||
users["ginachen94@gmail.com" ]= { password: hashPW("indiantreeonfire"),superuser:true, linksalt:"" };
|
||
}
|
||
InitDatabase();
|
||
|
||
function SerializeAll() {
|
||
const tables={ users, tickets, camps, settings };
|
||
fs.writeFileSync('foftickets.json', JSON.stringify(tables, null, 2), 'utf8');
|
||
}
|
||
|
||
function DeserializeAll() {
|
||
const data = fs.readFileSync('foftickets.json', 'utf8');
|
||
const tables = JSON.parse(data);
|
||
users=tables.users;
|
||
tickets=tables.tickets;
|
||
camps=tables.camps;
|
||
settings=tables.settings;
|
||
}
|
||
|
||
|
||
|
||
// Middleware setup
|
||
app.use(bodyParser.urlencoded({ extended: true }));
|
||
app.use(session({
|
||
secret: 'supersecretkey',
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
}));
|
||
|
||
// Middleware to protect routes
|
||
function requireLogin(req, res, next) {
|
||
if (req.session.username) return next();
|
||
req.session.returnTo=req.originalUrl;
|
||
return res.redirect('/login');
|
||
}
|
||
|
||
function requireSuperUser(req,res,next) {
|
||
if (req.session.superuser) return next();
|
||
req.session.returnTo=req.originalUrl;
|
||
return res.redirect('/login');
|
||
}
|
||
|
||
|
||
app.get('/styles.css', (req, res) => {
|
||
res.setHeader('Content-Type', 'text/css');
|
||
res.sendFile(path.join(__dirname, 'public', 'styles.css'));
|
||
});
|
||
|
||
function generateSecureToken(length = 8) {
|
||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||
let token = '';
|
||
const array = new Uint8Array(length);
|
||
crypto.getRandomValues(array);
|
||
for (let i = 0; i < length; i++) {
|
||
token += characters.charAt(array[i] % characters.length);
|
||
}
|
||
return token;
|
||
}
|
||
|
||
|
||
const postmark = require('postmark');
|
||
const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8');
|
||
|
||
app.get('/emaillink',(req,res) => {
|
||
res.send(`
|
||
<h1>Email me a login link</h1>
|
||
<form method="POST" action="/emaillink">
|
||
<label>Email Address: <input name="email"></label>
|
||
<button type="submit">Submit</button>`
|
||
)
|
||
});
|
||
|
||
|
||
app.get('/camps',requireSuperUser, (req,res) => {
|
||
const camplist={};
|
||
for (const c in camps) {
|
||
camplist[c]={ leader:camps[c].leader, issued:0, claimed:0, used:0 };
|
||
}
|
||
for (const t in tickets) {
|
||
const parts=t.split("-");
|
||
const campname=parts[0];
|
||
const ticketnum=Number(parts[1]);
|
||
if (tickets[t].status!="r") {
|
||
camplist[campname].issued+=1;
|
||
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||
if (tickets[t].status=="u") camplist[campname].used+=1;
|
||
}
|
||
}
|
||
return res.render("camps",{ username:req.session.username, superuser:req.session.superuser, camps:camplist });
|
||
})
|
||
|
||
app.post("/camps",requireSuperUser,(req,res) => {
|
||
const campname=req.body.campname;
|
||
const leader=req.body.leader;
|
||
const qty=Number(req.body.qty);
|
||
camps[campname]??={ leader:leader, lastid:0 };
|
||
for (let i=0; i<qty; i++) {
|
||
camps[campname].lastid+=1; // LOG
|
||
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" }; // LOG
|
||
}
|
||
EmailTickets(leader);
|
||
return res.redirect("/camps");
|
||
})
|
||
|
||
|
||
app.get('/editcamp', requireSuperUser, (req,res) => {
|
||
let campname=req.query.campname;
|
||
if (!camps[campname]) return res.redirect("/");
|
||
const edit={ username:req.session.username, superuser:req.session.superuser, campname:campname, leader:camps[campname].leader, tickets: {} };
|
||
for (const t in tickets) {
|
||
const parts=t.split("-");
|
||
const cname=parts[0];
|
||
const tnum=Number(parts[1]);
|
||
if (cname==campname) {
|
||
edit.tickets[t]={};
|
||
edit.tickets[t].owner=tickets[t].owner;
|
||
edit.tickets[t].offered=tickets[t].offered;
|
||
edit.tickets[t].status=tickets[t].status;
|
||
}
|
||
}
|
||
return res.render("editcamp",edit);
|
||
})
|
||
|
||
app.post('/moretickets', requireSuperUser, (req,res) => {
|
||
const qty=Number(req.body.qty);
|
||
const campname=req.body.campname;
|
||
if (campname in camps) for (let i=0; i<qty; i++) {
|
||
camps[campname].lastid++;
|
||
tickets[campname+'-'+camps[campname].lastid]={ owner:"", offered:camps[campname].leader, paid:0, status:"i" };
|
||
}
|
||
EmailTickets(camps[campname].leader);
|
||
const referer = req.get('Referer');
|
||
if (referer) return res.redirect(referer);
|
||
else return res.redirect('/');
|
||
});
|
||
|
||
app.get('/manytickets', requireLogin, (req,res) => {
|
||
let username=req.session.username;
|
||
const edit={ username:req.session.username, superuser:req.session.superuser, tickets: {} };
|
||
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status=="i") {
|
||
edit.tickets[t]={};
|
||
edit.tickets[t].offered=tickets[t].offered;
|
||
}
|
||
return res.render("manytickets",edit);
|
||
})
|
||
|
||
|
||
app.get('/mytickets',requireLogin, async (req,res)=> {
|
||
let username=req.session.username;
|
||
let claimed=0;
|
||
let owned=0;
|
||
let theticket="";
|
||
const edit={ username:req.session.username, superuser:req.session.superuser, tickets: {} };
|
||
for (const t in tickets) {
|
||
if (tickets[t].status=="i" && tickets[t].offered==username) { claimed++; tickets[t].owner=username; tickets[t].offered=""; } // LOG
|
||
if (tickets[t].status=="i" && tickets[t].owner==username) {
|
||
owned++;
|
||
if (owned==1) theticket=t; else theticket="";
|
||
edit.tickets[t]={};
|
||
edit.tickets[t].owner=tickets[t].owner;
|
||
edit.tickets[t].offered=tickets[t].offered;
|
||
}
|
||
}
|
||
let message="";
|
||
if (claimed>0) message="You have claimed "+claimed+" tickets.";
|
||
edit.message=message;
|
||
if (owned==0) return res.render("zerotickets",{ username:username, superuser:req.session.superuser, message:message });
|
||
else if (owned==1) {
|
||
const hash0=crypto.createHash('sha256');
|
||
const hash1=hash0.update(theticket+QRSalt);
|
||
const hash=hash1.digest("base64").slice(0,6);
|
||
const useurl=MainURL+'/useticket?t='+theticket+'&h='+hashQR(theticket,username);
|
||
const dataURL=await QRCode.toDataURL(useurl);
|
||
return res.render("oneticket",{ username:username, superuser:req.session.superuser, message:message, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL, useurl:useurl });
|
||
}
|
||
else return res.render("manytickets",edit);
|
||
});
|
||
|
||
app.post('/oneticket',requireLogin, async (req,res)=> {
|
||
let username=req.session.username;
|
||
let theticket=req.body.ticket;
|
||
let offered=req.body.offered;
|
||
let message="";
|
||
if (tickets[theticket].owner==username && tickets[theticket].status=="i") {
|
||
tickets[theticket].offered=offered; // LOG
|
||
EmailTickets(offered);
|
||
}
|
||
const hash0=crypto.createHash('sha256');
|
||
const hash1=hash0.update(theticket+QRSalt);
|
||
const hash=hash1.digest("base64").slice(0,6);
|
||
const dataURL=await QRCode.toDataURL(MainURL+'/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
||
return res.render("oneticket", { username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username), ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
||
});
|
||
|
||
|
||
|
||
app.post("/changestatus", requireSuperUser, (req,res) => {
|
||
const ticket=req.body.ticket;
|
||
tickets[ticket].status=req.body.status; // LOG
|
||
res.json({ message: 'Status received', status: req.body.status });
|
||
})
|
||
|
||
app.post("/updateoffered", requireLogin, (req,res) => {
|
||
const ticket=req.body.ticket;
|
||
const offered=req.body.offered;
|
||
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
||
else if (tickets[ticket].status=="u") res.status(500).send("Ticket "+ticket+" has already been used");
|
||
else if (tickets[ticket].status=="r") res.status(500).send("Ticket "+ticket+" was revoked");
|
||
else {
|
||
tickets[ticket].offered=offered; // LOG
|
||
res.json({ message: 'Updated owner of '+ticket+' to '+offered });
|
||
}
|
||
})
|
||
|
||
// Need a SuperUser version of this
|
||
app.post("/updateoffered2", requireLogin, (req,res) => {
|
||
const changes={};
|
||
for (ticket in req.body) if (tickets[ticket] && tickets[ticket].owner==req.session.username && req.body[ticket]!=tickets[ticket].offered) {
|
||
if (!(req.body[ticket] in changes)) changes[req.body[ticket]]=0;
|
||
changes[req.body[ticket]]++;
|
||
}
|
||
// Ok to bail here if we need to
|
||
for (ticket in req.body) if (tickets[ticket] && tickets[ticket].owner==req.session.username && req.body[ticket]!=tickets[ticket].offered) {
|
||
tickets[ticket].offered=req.body[ticket];
|
||
}
|
||
for (email in changes) EmailTickets(email);
|
||
return res.redirect("manytickets");
|
||
})
|
||
|
||
|
||
app.post("/updateoffered2su", requireSuperUser, (req,res) => {
|
||
const emaillist={};
|
||
for (name in req.body) {
|
||
let ticket=0;
|
||
if (name.endsWith("-owner" )) { ticket=name.slice(0,-6); tickets[ticket].owner =req.body[name]; }
|
||
else if (name.endsWith("-status" )) { ticket=name.slice(0,-7); tickets[ticket].status =req.body[name]; }
|
||
else if (name.endsWith("-offered")) {
|
||
ticket=name.slice(0,-8);
|
||
if (tickets[ticket].offered!=req.body[name]) {
|
||
tickets[ticket].offered=req.body[name];
|
||
emaillist[req.body[name]]=1;
|
||
}
|
||
}
|
||
}
|
||
for (email in emaillist) EmailTickets(email);
|
||
const referer = req.get('Referer');
|
||
if (referer) return res.redirect(referer);
|
||
else return res.redirect('/');
|
||
})
|
||
|
||
|
||
app.post("/updateticketsu", requireSuperUser, (req,res) => {
|
||
const ticket=req.body.ticket;
|
||
const owner=req.body.owner;
|
||
const offered=req.body.offered;
|
||
tickets[ticket].owner=req.body.owner; // LOG
|
||
tickets[ticket].offered=req.body.offered; // LOG
|
||
res.json({ message: 'Updated '+ticket });
|
||
})
|
||
|
||
|
||
app.get("/useticket",(req,res) => {
|
||
let ticket=req.query.t;
|
||
let hash=req.query.h;
|
||
if (!tickets[ticket]) return res.status(500).send("Ticket "+ticket+" not found.");
|
||
if (hashQR(ticket,tickets[ticket].owner)!=hash) return res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].owner);
|
||
if (tickets[ticket].status!="i") return res.status(500).send("Ticket "+ticket+" has already been used.");
|
||
if (settings['enable-email']) {
|
||
tickets[ticket].status="u"; // LOG
|
||
return res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
|
||
} else return res.send("<h1>Your ticket is good, "+tickets[ticket].owner+", but the server is not in Event Mode, so Ticket "+ticket+" is still valid.");
|
||
})
|
||
|
||
|
||
|
||
async function EmailTickets(email) {
|
||
let offered=0;
|
||
for (const ticket in tickets) if (tickets[ticket].offered==email) offered++;
|
||
if (offered==0) return;
|
||
const textbody="You have been offered "+offered+" tickets to Falls On Fire! To claim them, visit this link:\n"+GetMagicLink(email);
|
||
const htmlbody="You have been offered "+offered+" tickets to Falls On Fire! To claim them, <a href=\""+GetMagicLink(email)+"\">click here.</a>";
|
||
if (!settings['enable-email']) {
|
||
console.log("Email disabled. Would have sent to "+email+": "+textbody);
|
||
return;
|
||
}
|
||
await client.sendEmail({ From: "tickets@fallsonfire.net",
|
||
To: email,
|
||
Subject: "Falls on Fire: You've Got Tickets!",
|
||
TextBody: textbody,
|
||
HTMLBody: htmlbody
|
||
});
|
||
}
|
||
|
||
app.get('/testemail',
|
||
(req,res) => {
|
||
client.sendEmail({ From: 'tickets@fallsonfire.net',
|
||
To: 'teppy@egenesis.com',
|
||
Subject: 'Email from Ticketing System',
|
||
TextBody: 'This is a test email.',
|
||
HtmlBody: '<p>This is a test email.</p>',
|
||
}).then(() => {
|
||
console.log('Email sent');
|
||
res.send("<h1>Email sent</h1>");
|
||
}).catch((error) => {
|
||
console.error('Error sending email:', error);
|
||
res.send("<h1>Email failed</h1>");
|
||
})});
|
||
|
||
|
||
|
||
|
||
// Routes
|
||
app.get('/', (req, res) => {
|
||
if (req.session.username) return res.redirect("/mytickets");
|
||
return res.render("login", { superuser:false });
|
||
});
|
||
|
||
|
||
function HasPW(username) {
|
||
if (!(username in users)) return false;
|
||
if (!users[username]) return false;
|
||
if (!users[username].password) return false;
|
||
if (users[username].password==0) return false;
|
||
if (users[username].password=="") return false;
|
||
return true;
|
||
}
|
||
|
||
app.get('/login',(req,res) => {
|
||
const username=req.query.u;
|
||
const hash=req.query.h;
|
||
if (MagicLinkValid(username,hash)) {
|
||
req.session.username=username;
|
||
return res.redirect("/mytickets");
|
||
}
|
||
return res.render("login",{ message: "Normal Login Required." });
|
||
});
|
||
|
||
app.post('/login', (req, res) => {
|
||
const { username, password, superuser } = req.body;
|
||
if (users[username] && users[username].password === hashPW(password)) {
|
||
req.session.username = username;
|
||
req.session.superuser = users[username].superuser;
|
||
const redir=req.session.returnTo;
|
||
delete req.session.returnTo;
|
||
return res.redirect(redir || "/mytickets");
|
||
}
|
||
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
||
});
|
||
|
||
app.get('/logout', (req, res) => {
|
||
req.session.destroy(() => {
|
||
res.redirect('/');
|
||
});
|
||
});
|
||
|
||
|
||
app.get('/signup', (req, res) => {
|
||
res.send(`
|
||
<h1>Sign Up</h1>
|
||
<form method="POST" action="/signup">
|
||
<label>Username: <input type="text" name="username" required></label><br>
|
||
<label>Password: <input type="password" name="password" required></label><br>
|
||
<button type="submit">Sign Up</button>
|
||
</form>
|
||
<a href="/login">Log In</a>
|
||
`);
|
||
});
|
||
|
||
|
||
|
||
|
||
app.post('/signup', (req, res) => {
|
||
const { username, password } = req.body;
|
||
if (users[username]) {
|
||
return res.send('User already exists. <a href="/signup">Try again</a>');
|
||
}
|
||
users[username] = { password: hashPW(password) };
|
||
console.log("Created new account:",username);
|
||
res.redirect('/login');
|
||
});
|
||
|
||
app.get('/changepassword', (req, res) => {
|
||
res.send(`
|
||
<h1>Change Password</h1>
|
||
<form method="POST" action="/changepassword">
|
||
<label>Password: <input type="password" name="password1" required></label><br>
|
||
<label>Again: <input type="password" name="password2" required></label><br>
|
||
<button type="submit">Sign Up</button>
|
||
</form>
|
||
<a href="/">Home</a>
|
||
`);
|
||
});
|
||
|
||
app.post('/changepassword', (req, res) => {
|
||
const { password1, password2 } = req.body;
|
||
if (!req.session.username) {
|
||
return res.send('You are not logged in<a href="/">Back</a>');
|
||
}
|
||
if (password1!=password2) {
|
||
return res.send('Passwords do not match<a href="/">Back</a>');
|
||
}
|
||
users[req.session.username].password=hashPW(password1);
|
||
res.redirect('/');
|
||
});
|
||
|
||
|
||
app.post('/qrcode',requireLogin,async (req,res) => {
|
||
const username=req.session.username;
|
||
const ticket=req.body.ticket;
|
||
if (tickets[ticket].owner!=username) return res.status(500).send("Only a ticket owner can generate a QR code");
|
||
const URL=await QRCode.toDataURL(MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||
return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) });
|
||
})
|
||
|
||
app.post('/qrcodesu',requireSuperUser,async (req,res) => {
|
||
const ticket=req.body.ticket;
|
||
const username=tickets[ticket].owner;
|
||
const URL=await QRCode.toDataURL(MainURL+'/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||
return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) });
|
||
})
|
||
|
||
|
||
app.get('/settings',requireSuperUser, (req,res) => {
|
||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "" })
|
||
});
|
||
|
||
app.post('/wipedb',requireSuperUser, (req,res) => {
|
||
InitDatabase();
|
||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Wiped the database, but not the logfile." })
|
||
});
|
||
|
||
app.post('/serialize',requireSuperUser, (req,res) => {
|
||
SerializeAll();
|
||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Wrote database to disk." })
|
||
});
|
||
|
||
app.post('/deserialize',requireSuperUser, (req,res) => {
|
||
DeserializeAll();
|
||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Read database from disk." })
|
||
});
|
||
|
||
app.post('/purge',requireSuperUser, (req,res) => {
|
||
let count=0;
|
||
for (const t in tickets) if (tickets[t].status=='r') { count++; delete tickets[t]; }
|
||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Purged "+count+" revoked tickets" })
|
||
});
|
||
|
||
app.post('/update-setting', requireSuperUser, (req, res) => {
|
||
settings[req.body.name]=req.body.checked;
|
||
res.json({ success: true, message: 'Checkbox state updated successfully' });
|
||
});
|
||
|
||
|
||
app.get('/checkout', (req, res) => {
|
||
// We’ll render a payment form here
|
||
res.render('checkout2');
|
||
});
|
||
|
||
app.post('/charge', async (req, res) => {
|
||
try {
|
||
// Token or Payment Method ID from the client
|
||
const paymentMethodId = req.body.paymentMethodId;
|
||
|
||
// Create a PaymentIntent on the server
|
||
const return_url=base_url+'/mytickets';
|
||
const paymentIntent = await stripe.paymentIntents.create({
|
||
amount: 1999, // Amount in cents (e.g., $19.99)
|
||
currency: 'usd',
|
||
payment_method: paymentMethodId,
|
||
confirmation_method: 'automatic',
|
||
confirm: true, // Attempt to confirm the payment immediately
|
||
return_url: return_url,
|
||
});
|
||
|
||
// Check status of payment intent
|
||
if (paymentIntent.status === 'requires_action') {
|
||
// Additional action is required (e.g. 3D Secure)
|
||
res.json({
|
||
requiresAction: true,
|
||
paymentIntentClientSecret: paymentIntent.client_secret,
|
||
});
|
||
} else if (paymentIntent.status === 'succeeded') {
|
||
// Payment is complete
|
||
console.log("Returning json success: true");
|
||
res.json({ success: true, redirect_url: return_url });
|
||
} else {
|
||
res.json({ error: 'Invalid PaymentIntent status' });
|
||
}
|
||
} catch (error) {
|
||
console.error('Payment error:', error);
|
||
res.json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
|
||
// Start the server
|
||
app.listen(PORT, () => {
|
||
console.log(`Server is running at http://localhost:${PORT}`);
|
||
});
|