diff --git a/foftickets.js b/foftickets.js index 64dafda..940ce60 100644 --- a/foftickets.js +++ b/foftickets.js @@ -8,6 +8,7 @@ const path=require('path'); const app = express(); app.set('view engine','ejs'); app.use(express.json()); +app.use(express.static('public')); const PORT = 3000; const PWSalt="!SaltyMagic7283715374"; const QRSalt="!SaltyMagic5392370662"; @@ -19,15 +20,15 @@ const QRSalt="!SaltyMagic5392370662"; // + Email Link // + Signup // Change Password -// Issue Tickets -// Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code) +// + Issue Tickets +// + 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 // + Use Ticket -// Edit Tickets // // ToDo, Later -// Use a templating engine -// Store password hashed and salted +// + Use a templating engine +// + Store password hashed and salted // Make all HTML look nice // 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 }, }; -const camps = { "habitat": 0 }; +const camps = { "habitat": { issued: 6 } }; const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 } }; @@ -80,14 +81,15 @@ app.use(session({ // Middleware to protect routes function requireLogin(req, res, next) { - if (!req.session.username) return res.redirect('/login'); - next(); + if (req.session.username) return next(); + return res.redirect('/login'); } function requireSuperUser(req,res,next) { - console.log(req.session); - if (!req.session.superuser) return res.status(403).send(JSON.stringify( { error: "Forbidden without superuser flag" } )); - else next(); + if (req.session.superuser) return next(); + req.session.returnTo=req.originalUrl; + console.log("In requireSuperUser: ",req.session); + return res.redirect('/login'); } @@ -110,23 +112,6 @@ function generateSecureToken(length = 8) { const postmark = require('postmark'); const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8'); -app.get('/loginpassword',(req,res) => { - res.send(`

Sign Up

-
-
-
- -
`); -}) - -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) => { const tok=req.query.token; @@ -144,7 +129,7 @@ app.get('/emaillink',(req,res) => { ` ) -}) + }); // These can be owned, offered, of offering function categorizeTickets(username) { @@ -161,7 +146,6 @@ function categorizeTickets(username) { if (tickets[t].owner==username || tickets[t].offered==username) numtickets+=1; if (numtickets==1) thet=t; else thet=""; } - console.log("Simpleowner: "+simpleowner+" Offering "+offering+" SimpleOffered "+simpleoffered+" Numtickets "+numtickets); if (numtickets==0) return [ "none", ""]; if (numtickets >1) return [ "complex", ""]; 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 { let username=req.session.username; const [ cat, ticket ] = categorizeTickets(username); const simpledata={ username: username, ticket: ticket }; - console.log("Transfer type "+cat); if (cat=="none") return res.render("notickets",simpledata); if (cat=="error") return res.render("error",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) => { const ticket=req.body.ticket; 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"); else if (tickets[ticket].used) res.status(500).send("Ticket "+ticket+" has already been used"); else { @@ -234,6 +243,7 @@ app.post("/updateoffered", requireLogin, (req,res) => { } }) + app.get("/useticket",(req,res) => { let ticket=req.t; let hash=req.h; @@ -323,9 +333,7 @@ app.get('/signup', (req, res) => { function consumeToken(tok) { - console.log("ConsumeToken 1"); if (!(tok in tokens)) return false; - console.log("ConsumeToken 2"); const rval=tokens[tok].expires==0 || tokens[tok].expires>=Date.now(); delete tokensu[tokens[tok].username]; delete tokens[tok]; @@ -379,23 +387,27 @@ app.post('/changepassword', (req, res) => { }); app.get('/login', (req, res) => { - res.send(` -

Log In

-
-
-
- -
- Sign Up - `); -}); + console.log("In get /login: ",req.session); + res.send(` +

Log In

+
+
+
+ +
+ Sign Up + `); + }); 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; - 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. Try again'); }); @@ -409,11 +421,9 @@ app.get('/logout', (req, res) => { app.post('/qrcode',requireLogin,async (req,res) => { const username=req.session.username; 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"); 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 diff --git a/public/styles.css b/public/styles.css index 5047409..85a48ed 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,5 +1,3 @@ - - /* Modal container */ .modal { display: none; /* Hidden by default */ @@ -10,6 +8,8 @@ width: 100%; /* Full width */ height: 100%; /* Full height */ 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 */ @@ -19,6 +19,13 @@ padding: 20px; border: 1px solid #888; width: 80%; /* Adjust as needed */ + height: 80%; +} + +.qrcode-image { + object-fit: contain; + width: 80%; /* Adjust as needed */ + height: 80%; } /* Close button */ diff --git a/views/issue.ejs b/views/issue.ejs new file mode 100644 index 0000000..9484f46 --- /dev/null +++ b/views/issue.ejs @@ -0,0 +1,32 @@ + + + + Issue New Tickets + + + +

Welcome, <%= username %>!

+
+ + + + + + + + <% for (const [c,v] of Object.entries(camps)) { %> + + + + + + + <% } %> +
GroupQuantityClaimedUsed
<%=c%><%=v.issued%><%=v.claimed%><%=v.used%>
+ Issue new tickets:
+ Group Name:
+ Lead Email:
+ Qty:
+ +
+