changes
This commit is contained in:
106
foftickets.js
106
foftickets.js
@@ -8,6 +8,7 @@ const path=require('path');
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.set('view engine','ejs');
|
app.set('view engine','ejs');
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(express.static('public'));
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
const PWSalt="!SaltyMagic7283715374";
|
const PWSalt="!SaltyMagic7283715374";
|
||||||
const QRSalt="!SaltyMagic5392370662";
|
const QRSalt="!SaltyMagic5392370662";
|
||||||
@@ -19,15 +20,15 @@ const QRSalt="!SaltyMagic5392370662";
|
|||||||
// + Email Link
|
// + Email Link
|
||||||
// + Signup
|
// + Signup
|
||||||
// Change Password
|
// Change Password
|
||||||
// Issue Tickets
|
// + Issue Tickets
|
||||||
// Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code)
|
// + Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code)
|
||||||
|
// Split Transfer into admin (change owner, unused/used/cancelled pulldown) and public (no Used column) versions
|
||||||
// Claim Tickets
|
// Claim Tickets
|
||||||
// + Use Ticket
|
// + Use Ticket
|
||||||
// Edit Tickets
|
|
||||||
//
|
//
|
||||||
// ToDo, Later
|
// ToDo, Later
|
||||||
// Use a templating engine
|
// + Use a templating engine
|
||||||
// Store password hashed and salted
|
// + Store password hashed and salted
|
||||||
// Make all HTML look nice
|
// Make all HTML look nice
|
||||||
// Logging and Replay system
|
// Logging and Replay system
|
||||||
//
|
//
|
||||||
@@ -63,7 +64,7 @@ const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "", pa
|
|||||||
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const camps = { "habitat": 0 };
|
const camps = { "habitat": { issued: 6 } };
|
||||||
|
|
||||||
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
||||||
};
|
};
|
||||||
@@ -80,14 +81,15 @@ app.use(session({
|
|||||||
|
|
||||||
// Middleware to protect routes
|
// Middleware to protect routes
|
||||||
function requireLogin(req, res, next) {
|
function requireLogin(req, res, next) {
|
||||||
if (!req.session.username) return res.redirect('/login');
|
if (req.session.username) return next();
|
||||||
next();
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireSuperUser(req,res,next) {
|
function requireSuperUser(req,res,next) {
|
||||||
console.log(req.session);
|
if (req.session.superuser) return next();
|
||||||
if (!req.session.superuser) return res.status(403).send(JSON.stringify( { error: "Forbidden without superuser flag" } ));
|
req.session.returnTo=req.originalUrl;
|
||||||
else next();
|
console.log("In requireSuperUser: ",req.session);
|
||||||
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -110,23 +112,6 @@ function generateSecureToken(length = 8) {
|
|||||||
const postmark = require('postmark');
|
const postmark = require('postmark');
|
||||||
const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8');
|
const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8');
|
||||||
|
|
||||||
app.get('/loginpassword',(req,res) => {
|
|
||||||
res.send(`<h1>Sign Up</h1>
|
|
||||||
<form method="POST" action="/loginpassword">
|
|
||||||
<label>Username: <input type="text" name="username" required></label><br>
|
|
||||||
<label>Password: <input type="password" name="password" required></label><br>
|
|
||||||
<button type="submit">Log In</button>
|
|
||||||
</form>`);
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/loginpassword', (req, res) => {
|
|
||||||
const { username, password } = req.body;
|
|
||||||
if (username in users && hashPW(password)==users[username].password) {
|
|
||||||
req.session.username=username;
|
|
||||||
req.session.superuser=users[username].superuser;
|
|
||||||
res.redirect('/transfer');
|
|
||||||
} else { res.redirect('/transfer'); }
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/logintoken', (req, res) => {
|
app.get('/logintoken', (req, res) => {
|
||||||
const tok=req.query.token;
|
const tok=req.query.token;
|
||||||
@@ -144,7 +129,7 @@ app.get('/emaillink',(req,res) => {
|
|||||||
<label>Email Address: <input name="email"></label>
|
<label>Email Address: <input name="email"></label>
|
||||||
<button type="submit">Submit</button>`
|
<button type="submit">Submit</button>`
|
||||||
)
|
)
|
||||||
})
|
});
|
||||||
|
|
||||||
// These can be owned, offered, of offering
|
// These can be owned, offered, of offering
|
||||||
function categorizeTickets(username) {
|
function categorizeTickets(username) {
|
||||||
@@ -161,7 +146,6 @@ function categorizeTickets(username) {
|
|||||||
if (tickets[t].owner==username || tickets[t].offered==username) numtickets+=1;
|
if (tickets[t].owner==username || tickets[t].offered==username) numtickets+=1;
|
||||||
if (numtickets==1) thet=t; else thet="";
|
if (numtickets==1) thet=t; else thet="";
|
||||||
}
|
}
|
||||||
console.log("Simpleowner: "+simpleowner+" Offering "+offering+" SimpleOffered "+simpleoffered+" Numtickets "+numtickets);
|
|
||||||
if (numtickets==0) return [ "none", ""];
|
if (numtickets==0) return [ "none", ""];
|
||||||
if (numtickets >1) return [ "complex", ""];
|
if (numtickets >1) return [ "complex", ""];
|
||||||
if (simpleowner+offering+simpleoffered>1)
|
if (simpleowner+offering+simpleoffered>1)
|
||||||
@@ -173,6 +157,33 @@ function categorizeTickets(username) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/issue', requireSuperUser, (req,res) => {
|
||||||
|
const camplist={};
|
||||||
|
for (const t in tickets) {
|
||||||
|
const parts=t.split("-");
|
||||||
|
const campname=parts[0];
|
||||||
|
const ticketnum=Number(parts[1]);
|
||||||
|
camplist[campname]??={ issued:0, claimed:0, used:0 };
|
||||||
|
camplist[campname].issued+=1;
|
||||||
|
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
||||||
|
if (tickets[t].used) camplist[campname].used+=1;
|
||||||
|
}
|
||||||
|
return res.render("issue",{ username:"Teppy", camps:camplist });
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/issue",(req,res) => {
|
||||||
|
const campname=req.body.campname;
|
||||||
|
const email=req.body.email;
|
||||||
|
const qty=Number(req.body.qty);
|
||||||
|
camps[campname]??={ issued:0 };
|
||||||
|
for (let i=0; i<qty; i++) {
|
||||||
|
camps[campname].issued+=1;
|
||||||
|
tickets[campname+'-'+camps[campname].issued]={ owner: "", offered: email, used:false };
|
||||||
|
}
|
||||||
|
return res.redirect("/issue");
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// Big Kahuna
|
// Big Kahuna
|
||||||
// If you have zero tickets, show something saying that
|
// If you have zero tickets, show something saying that
|
||||||
// For each ticket owned, display options to offer it, use it, or (eventually) pay for it.
|
// For each ticket owned, display options to offer it, use it, or (eventually) pay for it.
|
||||||
@@ -182,7 +193,6 @@ app.get('/transfer', requireLogin, async (req,res) => {
|
|||||||
let username=req.session.username;
|
let username=req.session.username;
|
||||||
const [ cat, ticket ] = categorizeTickets(username);
|
const [ cat, ticket ] = categorizeTickets(username);
|
||||||
const simpledata={ username: username, ticket: ticket };
|
const simpledata={ username: username, ticket: ticket };
|
||||||
console.log("Transfer type "+cat);
|
|
||||||
if (cat=="none") return res.render("notickets",simpledata);
|
if (cat=="none") return res.render("notickets",simpledata);
|
||||||
if (cat=="error") return res.render("error",simpledata);
|
if (cat=="error") return res.render("error",simpledata);
|
||||||
if (cat=="simpleoffered") return res.render("claimone",simpledata);
|
if (cat=="simpleoffered") return res.render("claimone",simpledata);
|
||||||
@@ -225,7 +235,6 @@ app.post("/toggle", requireSuperUser, (req,res) => {
|
|||||||
app.post("/updateoffered", requireLogin, (req,res) => {
|
app.post("/updateoffered", requireLogin, (req,res) => {
|
||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
const offered=req.body.offered;
|
const offered=req.body.offered;
|
||||||
console.log("Ticket: ",ticket);
|
|
||||||
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
||||||
else if (tickets[ticket].used) res.status(500).send("Ticket "+ticket+" has already been used");
|
else if (tickets[ticket].used) res.status(500).send("Ticket "+ticket+" has already been used");
|
||||||
else {
|
else {
|
||||||
@@ -234,6 +243,7 @@ app.post("/updateoffered", requireLogin, (req,res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
app.get("/useticket",(req,res) => {
|
app.get("/useticket",(req,res) => {
|
||||||
let ticket=req.t;
|
let ticket=req.t;
|
||||||
let hash=req.h;
|
let hash=req.h;
|
||||||
@@ -323,9 +333,7 @@ app.get('/signup', (req, res) => {
|
|||||||
|
|
||||||
|
|
||||||
function consumeToken(tok) {
|
function consumeToken(tok) {
|
||||||
console.log("ConsumeToken 1");
|
|
||||||
if (!(tok in tokens)) return false;
|
if (!(tok in tokens)) return false;
|
||||||
console.log("ConsumeToken 2");
|
|
||||||
const rval=tokens[tok].expires==0 || tokens[tok].expires>=Date.now();
|
const rval=tokens[tok].expires==0 || tokens[tok].expires>=Date.now();
|
||||||
delete tokensu[tokens[tok].username];
|
delete tokensu[tokens[tok].username];
|
||||||
delete tokens[tok];
|
delete tokens[tok];
|
||||||
@@ -379,23 +387,27 @@ app.post('/changepassword', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/login', (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
res.send(`
|
console.log("In get /login: ",req.session);
|
||||||
<h1>Log In</h1>
|
res.send(`
|
||||||
<form method="POST" action="/login">
|
<h1>Log In</h1>
|
||||||
<label>Username: <input type="text" name="username" required></label><br>
|
<form method="POST" action="/login">
|
||||||
<label>Password: <input type="password" name="password" required></label><br>
|
<label>Username: <input type="text" name="username" required></label><br>
|
||||||
<button type="submit">Log In</button>
|
<label>Password: <input type="password" name="password" required></label><br>
|
||||||
</form>
|
<button type="submit">Log In</button>
|
||||||
<a href="/signup">Sign Up</a>
|
</form>
|
||||||
`);
|
<a href="/signup">Sign Up</a>
|
||||||
});
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/login', (req, res) => {
|
app.post('/login', (req, res) => {
|
||||||
const { username, password, superuser } = req.body;
|
const { username, password, superuser } = req.body;
|
||||||
if (users[username] && users[username].password === hashPW(password)) {
|
if (users[username] && users[username].password === hashPW(password)) {
|
||||||
req.session.username = username;
|
req.session.username = username;
|
||||||
req.session.superuser = users[username].superuser;
|
req.session.superuser = users[username].superuser;
|
||||||
return res.redirect('/transfer');
|
console.log("In post /login",req.session);
|
||||||
|
const redir=req.session.returnTo;
|
||||||
|
delete req.session.returnTo;
|
||||||
|
return res.redirect(redir || "/transfer");
|
||||||
}
|
}
|
||||||
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
||||||
});
|
});
|
||||||
@@ -409,11 +421,9 @@ app.get('/logout', (req, res) => {
|
|||||||
app.post('/qrcode',requireLogin,async (req,res) => {
|
app.post('/qrcode',requireLogin,async (req,res) => {
|
||||||
const username=req.session.username;
|
const username=req.session.username;
|
||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
console.log("Body: ",req.body);
|
|
||||||
console.log("Tickets["+ticket+"]",tickets[ticket]);
|
|
||||||
if (tickets[ticket].owner!=username) return res.status(500).send("Only a ticket owner can generate a QR code");
|
if (tickets[ticket].owner!=username) return res.status(500).send("Only a ticket owner can generate a QR code");
|
||||||
const URL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
const URL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
||||||
return res.send({ qrcode: URL });
|
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
||||||
})
|
})
|
||||||
|
|
||||||
// Protected routes
|
// Protected routes
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
/* Modal container */
|
/* Modal container */
|
||||||
.modal {
|
.modal {
|
||||||
display: none; /* Hidden by default */
|
display: none; /* Hidden by default */
|
||||||
@@ -10,6 +8,8 @@
|
|||||||
width: 100%; /* Full width */
|
width: 100%; /* Full width */
|
||||||
height: 100%; /* Full height */
|
height: 100%; /* Full height */
|
||||||
background-color: rgba(0, 0, 0, 0.5); /* Black with opacity */
|
background-color: rgba(0, 0, 0, 0.5); /* Black with opacity */
|
||||||
|
align-items: center; /* Vertically center the modal */
|
||||||
|
justify-content: center; /* Horizontally center the modal */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal content */
|
/* Modal content */
|
||||||
@@ -19,6 +19,13 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid #888;
|
border: 1px solid #888;
|
||||||
width: 80%; /* Adjust as needed */
|
width: 80%; /* Adjust as needed */
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-image {
|
||||||
|
object-fit: contain;
|
||||||
|
width: 80%; /* Adjust as needed */
|
||||||
|
height: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Close button */
|
/* Close button */
|
||||||
|
|||||||
32
views/issue.ejs
Normal file
32
views/issue.ejs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Issue New Tickets</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome, <%= username %>!</h1>
|
||||||
|
<form id="editor" action="/issue" method="post">
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Group</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Claimed</th>
|
||||||
|
<th>Used</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const [c,v] of Object.entries(camps)) { %>
|
||||||
|
<tr>
|
||||||
|
<td><%=c%></td>
|
||||||
|
<td><%=v.issued%></td>
|
||||||
|
<td><%=v.claimed%></td>
|
||||||
|
<td><%=v.used%></td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
</table>
|
||||||
|
Issue new tickets:<br>
|
||||||
|
Group Name: <input type="edit" name="campname"><br>
|
||||||
|
Lead Email: <input type="edit" name="email"><br>
|
||||||
|
Qty: <input type="edit" name="qty"><br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="QRShow" class="modal">
|
||||||
|
<div id="QRBackground" class="modal-content">
|
||||||
|
<p id="QRBanner">Generating QR Code...</p>
|
||||||
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<h1>Welcome, <%= username %>!</h1>
|
<h1>Welcome, <%= username %>!</h1>
|
||||||
<form id="editor">
|
<form id="editor">
|
||||||
<div id="server-response">Server Ready</div>
|
<div id="server-response">Server Ready</div>
|
||||||
@@ -31,6 +37,8 @@
|
|||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
// JavaScript to change the form element
|
// JavaScript to change the form element
|
||||||
|
const blankimage=document.getElementById("QRCodeImage").src;
|
||||||
|
console.log("Blank is ",blankimage);
|
||||||
const ResponseDisplay = document.getElementById("server-response");
|
const ResponseDisplay = document.getElementById("server-response");
|
||||||
let ResponseStack = 0;
|
let ResponseStack = 0;
|
||||||
let ResponseError="";
|
let ResponseError="";
|
||||||
@@ -59,6 +67,7 @@ function toggleUsed(el) {
|
|||||||
|
|
||||||
document.body.addEventListener("click", event => {
|
document.body.addEventListener("click", event => {
|
||||||
const id=event.target.id;
|
const id=event.target.id;
|
||||||
|
console.log("Click event on id ",id);
|
||||||
if (id.endsWith("-action")) {
|
if (id.endsWith("-action")) {
|
||||||
const id0=id.slice(0,-7);
|
const id0=id.slice(0,-7);
|
||||||
if (ooEdits[id0]) {
|
if (ooEdits[id0]) {
|
||||||
@@ -68,23 +77,37 @@ document.body.addEventListener("click", event => {
|
|||||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
fetch('/updateoffered',fetchtable)
|
fetch('/updateoffered',fetchtable)
|
||||||
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||||
.then( data => { UpdateSR(-1); event.target.textContent="QRCode"; } )
|
.then( data => { UpdateSR(-1); event.target.textContent="QRCode"; ooEdits[id0]=false; } )
|
||||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||||
} else {
|
} else {
|
||||||
|
const modal = document.getElementById('QRShow');
|
||||||
|
const qrbanner=document.getElementById('QRBanner');
|
||||||
|
const qrcodeimage=document.getElementById('QRCodeImage');
|
||||||
|
const closeModalSpan = document.querySelector('.close');
|
||||||
|
modal.style.display = 'flex';
|
||||||
const js=JSON.stringify( { ticket: id0 } );
|
const js=JSON.stringify( { ticket: id0 } );
|
||||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||||
|
qrcodeimage.src=blankimage;
|
||||||
fetch('/qrcode',fetchtable)
|
fetch('/qrcode',fetchtable)
|
||||||
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||||
.then( data => { console.log("Data is: ",data); } )
|
.then( data => {
|
||||||
|
console.log("Data is: ",data);
|
||||||
|
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
||||||
|
qrcodeimage.src=data.qrcode;
|
||||||
|
} )
|
||||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||||
|
|
||||||
console.log("QRCode ",id0);
|
console.log("QRCode ",id0);
|
||||||
}
|
}
|
||||||
}
|
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
||||||
|
const modal = document.getElementById('QRShow');
|
||||||
|
modal.style.display="none";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const checkboxes = document.querySelectorAll("[id$=-used]");
|
const checkboxes = document.querySelectorAll("[id$=-used]");
|
||||||
checkboxes.forEach(el => {
|
checkboxes.forEach(el => {
|
||||||
const UsedCheckbox = document.getElementById(el.id);
|
const UsedCheckbox = document.getElementById(el.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user