changes
This commit is contained in:
@@ -27,9 +27,11 @@ const QRSalt ="!SaltyMagic5392370662";
|
|||||||
// + Claim Any Tickets
|
// + Claim Any Tickets
|
||||||
// + Purge Revoked Function (Admin)
|
// + Purge Revoked Function (Admin)
|
||||||
// + Issue Tickets from Camp Admin Page
|
// + Issue Tickets from Camp Admin Page
|
||||||
// Make one Update button on manytickets (User)
|
// + Send email when new tickets are issued/offered
|
||||||
// Make one Update button on editcamp (Admin)
|
// + Make one Update button on editcamp (Admin)
|
||||||
// Send email when new tickets are issued/offered
|
// + 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
|
// Magic-link Login System
|
||||||
// Deactivate individual magic links (User)
|
// Deactivate individual magic links (User)
|
||||||
// Deactivate individual magic links (Admin)
|
// Deactivate individual magic links (Admin)
|
||||||
@@ -39,8 +41,9 @@ const QRSalt ="!SaltyMagic5392370662";
|
|||||||
// + 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(?)
|
||||||
// Stripe Integration
|
// Stripe Integration
|
||||||
|
// More efficent data structure: TicketsByCamp, TicketsByOffered, TicketsByOwner
|
||||||
//
|
//
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -110,6 +113,7 @@ function MagicLinkValid(email,hash) {
|
|||||||
const users={};
|
const users={};
|
||||||
const tickets={};
|
const tickets={};
|
||||||
const camps={};
|
const camps={};
|
||||||
|
const settings={};
|
||||||
|
|
||||||
function InitDatabase() {
|
function InitDatabase() {
|
||||||
for (const key in users ) delete users[key];
|
for (const key in users ) delete users[key];
|
||||||
@@ -206,6 +210,7 @@ app.post("/camps",requireSuperUser,(req,res) => {
|
|||||||
|
|
||||||
app.get('/editcamp', requireSuperUser, (req,res) => {
|
app.get('/editcamp', requireSuperUser, (req,res) => {
|
||||||
let campname=req.query.campname;
|
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: {} };
|
const edit={ username:req.session.username, superuser:req.session.superuser, campname:campname, leader:camps[campname].leader, tickets: {} };
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
const parts=t.split("-");
|
const parts=t.split("-");
|
||||||
@@ -328,6 +333,27 @@ app.post("/updateoffered2", requireLogin, (req,res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
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) => {
|
app.post("/updateticketsu", requireSuperUser, (req,res) => {
|
||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
const owner=req.body.owner;
|
const owner=req.body.owner;
|
||||||
@@ -344,13 +370,16 @@ app.get("/useticket",(req,res) => {
|
|||||||
if (!tickets[ticket]) return res.status(500).send("Ticket "+ticket+" not found.");
|
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 (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 (tickets[ticket].status!="i") return res.status(500).send("Ticket "+ticket+" has already been used.");
|
||||||
|
if (settings['enable-email']) {
|
||||||
tickets[ticket].status="u"; // LOG
|
tickets[ticket].status="u"; // LOG
|
||||||
return res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
|
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) {
|
async function EmailTickets(email) {
|
||||||
|
if (!settings['enable-email']) return;
|
||||||
let offered=0;
|
let offered=0;
|
||||||
for (const ticket in tickets) if (tickets[ticket].offered==email) offered++;
|
for (const ticket in tickets) if (tickets[ticket].offered==email) offered++;
|
||||||
if (offered==0) return;
|
if (offered==0) return;
|
||||||
@@ -505,6 +534,11 @@ app.post('/purge',requireSuperUser, (req,res) => {
|
|||||||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Purged "+count+" revoked tickets" })
|
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' });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
|
|||||||
@@ -13,12 +13,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form id="editor">
|
<form id="editor" method="POST" action="/updateoffered2su">
|
||||||
<div id="server-response">Server Ready</div>
|
<div id="server-response">Server Ready</div>
|
||||||
<br>
|
Camp Namex: <%=campname%><br>
|
||||||
Camp Name: <%=campname%><br>
|
|
||||||
Camp Leader: <%=leader%>
|
Camp Leader: <%=leader%>
|
||||||
|
|
||||||
<table border="1">
|
<table border="1">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Ticket#</th>
|
<th>Ticket#</th>
|
||||||
@@ -30,10 +28,10 @@
|
|||||||
<% for (const t in tickets) { %>
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><%=t%></td>
|
||||||
<td><input type="edit" value="<%=tickets[t].owner%>" id="<%=t%>-owner"> </td>
|
<td><input type="text" class="owner" value="<%=tickets[t].owner%>" name="<%=t%>-owner"> </td>
|
||||||
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
<td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>-offered"></td>
|
||||||
<td>
|
<td>
|
||||||
<select id="<%=t%>-status" name="status">
|
<select class="status" name="<%=t%>-status">
|
||||||
<option value="i"<%=tickets[t].status=="i" ? " selected" : ""%>>Issued</option>
|
<option value="i"<%=tickets[t].status=="i" ? " selected" : ""%>>Issued</option>
|
||||||
<option value="u"<%=tickets[t].status=="u" ? " selected" : ""%>>Used</option>
|
<option value="u"<%=tickets[t].status=="u" ? " selected" : ""%>>Used</option>
|
||||||
<option value="r"<%=tickets[t].status=="r" ? " selected" : ""%>>Revoked</option>
|
<option value="r"<%=tickets[t].status=="r" ? " selected" : ""%>>Revoked</option>
|
||||||
@@ -45,10 +43,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
|
||||||
<form action='/moretickets' method='post'>
|
|
||||||
<input type="hidden" name="campname" value="<%=campname%>">
|
<input type="hidden" name="campname" value="<%=campname%>">
|
||||||
|
<button type="submit">Update Tickets</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<form action='/moretickets' method='post'>
|
||||||
Issue Tickets (Qty): <input type="edit" name="qty"><br>
|
Issue Tickets (Qty): <input type="edit" name="qty"><br>
|
||||||
|
<input type="hidden" name="campname" value="<%=campname%>">
|
||||||
<button type="submit">Issue</button>
|
<button type="submit">Issue</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,47 +60,12 @@
|
|||||||
|
|
||||||
// JavaScript to change the form element
|
// JavaScript to change the form element
|
||||||
const blankimage=document.getElementById("QRCodeImage").src;
|
const blankimage=document.getElementById("QRCodeImage").src;
|
||||||
const ResponseDisplay = document.getElementById("server-response");
|
|
||||||
let ResponseStack = 0;
|
|
||||||
let ResponseError="";
|
|
||||||
let ooEdits={}; // Owner or Offer has been edited
|
|
||||||
|
|
||||||
function UpdateSR(delta) {
|
|
||||||
ResponseStack+=delta;
|
|
||||||
if (ResponseError!="") ResponseDisplay.textContent=ResponseError;
|
|
||||||
else if (ResponseStack>0) ResponseDisplay.textContent="Waiting for Server";
|
|
||||||
else ResponseDisplay.textContent="Server Ready";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function changeStatus(el) {
|
|
||||||
console.log("Change Status: ",el.target.value);
|
|
||||||
const id=el.target.id.slice(0,-7);
|
|
||||||
const js=JSON.stringify( { ticket: id, status: el.target.value } );
|
|
||||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
|
||||||
UpdateSR(1);
|
|
||||||
fetch('/changestatus',fetchtable)
|
|
||||||
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
|
||||||
.then( data => UpdateSR(-1) )
|
|
||||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
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]) {
|
|
||||||
UpdateSR(1);
|
|
||||||
const ownerEdit=document.getElementById(id0+"-owner");
|
|
||||||
const offeredEdit=document.getElementById(id0+"-offered");
|
|
||||||
const js=JSON.stringify( { ticket: id0, owner:ownerEdit.value, offered: offeredEdit.value } );
|
|
||||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
|
||||||
fetch('/updateticketsu',fetchtable)
|
|
||||||
.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"; ooEdits[id0]=false; } )
|
|
||||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
|
||||||
} else {
|
|
||||||
const modal = document.getElementById('QRShow');
|
const modal = document.getElementById('QRShow');
|
||||||
const qrbanner=document.getElementById('QRBanner');
|
const qrbanner=document.getElementById('QRBanner');
|
||||||
const qrcodeimage=document.getElementById('QRCodeImage');
|
const qrcodeimage=document.getElementById('QRCodeImage');
|
||||||
@@ -115,10 +81,7 @@ document.body.addEventListener("click", event => {
|
|||||||
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
||||||
qrcodeimage.src=data.qrcode;
|
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; })
|
||||||
|
|
||||||
console.log("QRCode ",id0);
|
|
||||||
}
|
|
||||||
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
||||||
const modal = document.getElementById('QRShow');
|
const modal = document.getElementById('QRShow');
|
||||||
modal.style.display="none";
|
modal.style.display="none";
|
||||||
@@ -128,21 +91,14 @@ document.body.addEventListener("click", event => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const pulldowns = document.querySelectorAll("[id$=-status]");
|
const MessageArea=document.getElementById("message");
|
||||||
pulldowns.forEach(el => {
|
const pulldowns = document.getElementsByClassName("status");
|
||||||
const pd = document.getElementById(el.id);
|
for (let i=0; i<pulldowns.length; i++) pulldowns[i].addEventListener('change',(event) => MessageArea.textContent="Be sure to use the Update Tickets button.");
|
||||||
pd.addEventListener("change",changeStatus);
|
const owners = document.getElementsByClassName("owner");
|
||||||
});
|
for (let i=0; i<owners.length; i++) owners[i].addEventListener('input',(event)=>MessageArea.textContent= "Be sure to use the Update Tickets button.");
|
||||||
|
const offereds = document.getElementsByClassName("offered");
|
||||||
|
for (let i=0; i<offereds.length; i++) offereds[i].addEventListener('input',(event)=>MessageArea.textContent= "Be sure to use the Update Tickets button.");
|
||||||
|
|
||||||
const owners = document.querySelectorAll("[id$=-owner]");
|
|
||||||
owners.forEach(el => {
|
|
||||||
const id0=el.id.slice(0,-6);
|
|
||||||
const OwnerEdit = document.getElementById(id0+"-owner" );
|
|
||||||
const OfferedEdit = document.getElementById(id0+"-offered");
|
|
||||||
const ActionButton = document.getElementById(id0+"-action" );
|
|
||||||
OwnerEdit .addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
|
||||||
OfferedEdit.addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -46,29 +46,12 @@ let ResponseStack = 0;
|
|||||||
let ResponseError="";
|
let ResponseError="";
|
||||||
let ooEdits={}; // Owner or Offer has been edited
|
let ooEdits={}; // Owner or Offer has been edited
|
||||||
|
|
||||||
function UpdateSR(delta) {
|
|
||||||
ResponseStack+=delta;
|
|
||||||
if (ResponseError!="") ResponseDisplay.textContent=ResponseError;
|
|
||||||
else if (ResponseStack>0) ResponseDisplay.textContent="Waiting for Server";
|
|
||||||
else ResponseDisplay.textContent="Server Ready";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
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]) {
|
|
||||||
UpdateSR(1);
|
|
||||||
const offeredEdit=document.getElementById(id0+"-offered");
|
|
||||||
const js=JSON.stringify( { ticket: id0, offered: offeredEdit.value } );
|
|
||||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
|
||||||
fetch('/updateoffered',fetchtable)
|
|
||||||
.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"; ooEdits[id0]=false; } )
|
|
||||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
|
||||||
} else {
|
|
||||||
const modal = document.getElementById('QRShow');
|
const modal = document.getElementById('QRShow');
|
||||||
const qrbanner=document.getElementById('QRBanner');
|
const qrbanner=document.getElementById('QRBanner');
|
||||||
const qrcodeimage=document.getElementById('QRCodeImage');
|
const qrcodeimage=document.getElementById('QRCodeImage');
|
||||||
@@ -84,22 +67,19 @@ document.body.addEventListener("click", event => {
|
|||||||
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
qrbanner.innerText="Ticket: "+id0+" Owner: "+data.owner;
|
||||||
qrcodeimage.src=data.qrcode;
|
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; })
|
||||||
|
|
||||||
console.log("QRCode ",id0);
|
|
||||||
}
|
|
||||||
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
} else if (["QRShow","QRCodeImage","QRBackground","QRBanner"].includes(id)) {
|
||||||
const modal = document.getElementById('QRShow');
|
const modal = document.getElementById('QRShow');
|
||||||
modal.style.display="none";
|
modal.style.display="none";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const offereds = document.getElementsByClassName("offered");
|
|
||||||
const MessageArea=document.getElementById("message");
|
const MessageArea=document.getElementById("message");
|
||||||
console.log("Offereds is ",offereds[0]);
|
const offereds = document.getElementsByClassName("offered");
|
||||||
for (let i=0; i<offereds.length; i++) offereds[i].addEventListener('input',(event)=>MessageArea.textContent= "Be sure to use the Update Offered button.");
|
for (let i=0; i<offereds.length; i++) offereds[i].addEventListener('input',(event)=>MessageArea.textContent= "Be sure to use the Update Offered button.");
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<%- include('partials/nav') %>
|
<%- include('partials/nav') %>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<input type="checkbox" class="setting" name="enable-email">Enable Email<br>
|
||||||
|
<input type="checkbox" class="setting" name="enable-ticket-use">Enable Ticket Use<br>
|
||||||
|
<input type="checkbox" class="setting" name="enable-credit-cards">Enable Credit Cards<br>
|
||||||
<form action='/purge' method='post'>
|
<form action='/purge' method='post'>
|
||||||
<button type="submit" >Purge Revoked Tickets</button>
|
<button type="submit" >Purge Revoked Tickets</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -15,5 +18,22 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
const MessageArea=document.getElementById("message");
|
||||||
|
const checkboxes = document.getElementsByClassName("setting");
|
||||||
|
for (let i=0; i<checkboxes.length; i++) checkboxes[i].addEventListener('change',(event) => {
|
||||||
|
console.log(event);
|
||||||
|
fetch('/update-setting',{ method:"POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name:event.target.name, checked:event.target.checked }) })
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error("Network Error: Server did not respond.");
|
||||||
|
MessageArea.textContent="Updated "+event.target.name;
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error updating checkbox state:', error));
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user