Compare commits
10 Commits
84290f9c46
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dc45639450 | |||
| 395a64c6fb | |||
| e79bb5f76d | |||
| 31cac4e423 | |||
| 3a5db74728 | |||
| cd1606c9df | |||
| d074e4b171 | |||
| f059b231c7 | |||
| 938f2d5564 | |||
| 74808f3fb2 |
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
PORT=3000
|
||||
BASE_URL=http://localhost:3000
|
||||
STRIPE_SECRET_KEY=sk_test_51QZlMSCHHjDgpHosJ9y6P9rtqJwmaGogCFy1gsZ1HDJyT98LlmSrRFJByawVJbvJh5DEhH6lIcWitsB8vJfPtkus00AomfjTaA
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
.env
|
||||
*~
|
||||
temp
|
||||
public/styles.css
|
||||
@@ -62,6 +62,16 @@ const QRSalt ="!SaltyMagic5392370662";
|
||||
// + See how much each camp/ticket has paid (Admin)
|
||||
// + Purchase individual open camping tickets
|
||||
// + Deactivate individual magic links (Admin)
|
||||
// + Get it working again online
|
||||
// Make all HTML look nice
|
||||
// If you only have a magic link, supress "Change Password"
|
||||
// Once a ticket is used, disallow transfer (oneticket)
|
||||
// Once a ticket is used, disallow transfer (manytickets)
|
||||
// Why does the live version redirect back to localhost?
|
||||
// Make sure open camping tickets actually send email
|
||||
// Don't hardcode MainURL and PORT
|
||||
// Limit number of Open Camping tickets
|
||||
// + Make sure subsequent FB imports don't add duplicate tickets
|
||||
// Maybe:
|
||||
// Deactivate individual magic links (User)
|
||||
// Option to "Email me my QR Code"
|
||||
@@ -70,7 +80,6 @@ const QRSalt ="!SaltyMagic5392370662";
|
||||
// + Use a templating engine
|
||||
// + Store password hashed and salted
|
||||
// + Stripe Integration
|
||||
// Make all HTML look nice
|
||||
// Logging and Replay system(?)
|
||||
// More efficent data structure: TicketsByCamp, TicketsByOffered, TicketsByOwner
|
||||
//
|
||||
@@ -175,7 +184,7 @@ app.use((req, res, next) => {
|
||||
let users={};
|
||||
let tickets={};
|
||||
let camps={};
|
||||
let settings={ "enable-transfer":true };
|
||||
let settings={ "enable-transfer":true, "open-limit":0 };
|
||||
|
||||
function InitDatabase() {
|
||||
for (const key in users ) delete users[key];
|
||||
@@ -650,13 +659,29 @@ app.post('/qrcodesu',requireSuperUser,async (req,res) => {
|
||||
return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) });
|
||||
})
|
||||
|
||||
function opencount() {
|
||||
let rval=0;
|
||||
for (t in tickets) if (t.startsWith("open-")) if (tickets[t].status!='r') rval++;
|
||||
return rval;
|
||||
}
|
||||
|
||||
app.get('/settings',requireSuperUser, (req,res) => {
|
||||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, settings:settings, message: "" })
|
||||
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, settings:settings, opencount:opencount(), message: "" })
|
||||
});
|
||||
|
||||
app.post('/addopen',requireSuperUser,(req,res) => {
|
||||
settings['open-limit']+=Number(req.body.addopen);
|
||||
return res.redirect('/settings');
|
||||
});
|
||||
|
||||
|
||||
app.post('/importfb',requireSuperUser,upload.single("file"),(req,res) => {
|
||||
console.log("File name:", req.file.originalname);
|
||||
let emails={};
|
||||
for (t in tickets) {
|
||||
if (tickets[t].offered) emails[tickets[t].offered]=1;
|
||||
if (tickets[t].owner ) emails[tickets[t].owner ]=1;
|
||||
}
|
||||
const contents=req.file.buffer.toString();
|
||||
csvParse.parse(contents, { columns: true, trim: true }, (err, records) => {
|
||||
if (err) {
|
||||
@@ -665,13 +690,14 @@ app.post('/importfb',requireSuperUser,upload.single("file"),(req,res) => {
|
||||
return res.redirect("/settings");
|
||||
}
|
||||
let count=0;
|
||||
for (const item of records) {
|
||||
for (const item of records) if (item.email in emails) {
|
||||
if (!camps[item.camp]) camps[item.camp]={ leader:"", lastid:0 };
|
||||
camps[item.camp].lastid++;
|
||||
const ticket=item.camp+"-"+camps[item.camp].lastid;
|
||||
tickets[ticket]={ owner:"", offered: item.email, paid:0.00, status:"i" };
|
||||
console.log("Offered ",ticket," to ",item.email);
|
||||
count++;
|
||||
emails[item.email]=1;
|
||||
}
|
||||
req.session.message="Imported "+count+" Frostburn-style records.";
|
||||
return res.redirect("/settings");
|
||||
@@ -690,7 +716,7 @@ app.post('/serialize',requireSuperUser, async (req,res) => {
|
||||
|
||||
app.post('/deserialize',requireSuperUser, (req,res) => {
|
||||
DeserializeAll();
|
||||
return res.redirect("/"); // Since we may be overwriting session
|
||||
return res.redirect("/settings"); // Since we may be overwriting session
|
||||
});
|
||||
|
||||
app.post('/purge',requireSuperUser, (req,res) => {
|
||||
@@ -745,6 +771,7 @@ function do_payforwhat(payforwhat) {
|
||||
camps[payforwhat.camp].lastid++;
|
||||
tickets[payforwhat.camp+"-"+camps[payforwhat.camp].lastid]={ owner:payforwhat.email, offered:"",paid:100.0*payforwhat.amounteach,status:"i" };
|
||||
}
|
||||
EmailTickets(payforwhat.email);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,6 +788,7 @@ app.post('/buy',requireLogin,(req,res) => {
|
||||
|
||||
|
||||
|
||||
|
||||
app.post('/charge', requireLogin, async (req, res) => {
|
||||
const payforwhat=req.body.payforwhat;
|
||||
if (!check_payforwhat(payforwhat,req)) return res.json({ error: 'Invalid PayForWhat' });
|
||||
|
||||
1855
package-lock.json
generated
Normal file
1855
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"csv-parse": "^5.6.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemon": "^3.1.9",
|
||||
"postmark": "^4.0.5",
|
||||
"qrcode": "^1.5.4",
|
||||
"stripe": "^17.6.0"
|
||||
}
|
||||
}
|
||||
@@ -35,30 +35,43 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 240px auto;
|
||||
grid-template-rows: 40px auto;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
}
|
||||
|
||||
.nav {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
background-color: #FFC0C0;
|
||||
.nav-header {
|
||||
font-size: 1.5rem;
|
||||
padding: 10px 15px;
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1;
|
||||
background-color: #C0C0C0;
|
||||
}
|
||||
|
||||
.nav-items {
|
||||
padding: 10px 15px;
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 3;
|
||||
background-color: #C0C0C0;
|
||||
}
|
||||
|
||||
.message {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
background-color: #C0FFC0;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
background-color: #C0C0C0;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
background-color: #C0C0FF;
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<body>
|
||||
<%- include('partials/nav') %>
|
||||
<div class="content">
|
||||
Change Password
|
||||
<h1>Change Password</h1>
|
||||
<form id="editor" method="POST" action="/changepassword">
|
||||
Old Password:<input type="password" name="password0"><br>
|
||||
New Password:<input type="password" name="password1"><br>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>You don't have any tickets</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>No tickets for you!!</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Your Tickets</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome, <%= username %>!</h1>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Owner</th>
|
||||
<th>Offered To</th>
|
||||
<th>QRCode</th>
|
||||
</tr>
|
||||
<% for (const t in tlist) { %>
|
||||
<tr>
|
||||
<td> <%= t %> </td>
|
||||
<td> <%= tlist[t].owner %> </td>
|
||||
<td> <%= tlist[t].offered %> </td>
|
||||
<td> <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> </td>
|
||||
</tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +1,28 @@
|
||||
<!-- views/partials/nav.ejs -->
|
||||
<div class="nav">
|
||||
Falls on Fire<br>
|
||||
<div class="nav-header">
|
||||
Falls on Fire
|
||||
</div>
|
||||
<div class="nav-items">
|
||||
<a href="/buy">Buy Tickets</a><br>
|
||||
<%if (typeof username!='undefined' && username) {%>
|
||||
<a href="/mytickets">View My Tickets</a><br>
|
||||
<%}%>
|
||||
<%if (typeof superuser!='undefined' && superuser) {%>
|
||||
<a href="/camps">View Camps (Admin)</a><br>
|
||||
<a href="/settings">Settings (Admin)</a><br>
|
||||
<%}%>
|
||||
<%if (typeof username!='undefined' && username) {%>
|
||||
<a href="/changepassword">Change Password</a><br>
|
||||
<a href="/logout">Log Out</a><br>
|
||||
<%} else {%>
|
||||
<a href="/create">Create Account</a><br>
|
||||
<a href="/login">Log In</a><br>
|
||||
<%}%>
|
||||
<%if (typeof username!='undefined' && username) {%>
|
||||
<a href="/mytickets">View My Tickets</a><br>
|
||||
<%}%>
|
||||
<%if (typeof superuser!='undefined' && superuser) {%>
|
||||
<a href="/camps">View Camps (Admin)</a><br>
|
||||
<a href="/settings">Settings (Admin)</a><br>
|
||||
<%}%>
|
||||
<%if (typeof username!='undefined' && username) {%>
|
||||
<a href="/changepassword">Change Password</a><br>
|
||||
<a href="/logout">Log Out</a><br>
|
||||
<%} else {%>
|
||||
<a href="/create">Create Account</a><br>
|
||||
<a href="/login">Log In</a><br>
|
||||
<%}%>
|
||||
</div>
|
||||
<div class="message" id="message">
|
||||
<% if (typeof commonData.message !== 'undefined') { %>
|
||||
<p><%= commonData.message %></p>
|
||||
<% } %>
|
||||
<% if (typeof commonData.message !== 'undefined') { %>
|
||||
<p><%= commonData.message %></p>
|
||||
<% } %>
|
||||
</div>
|
||||
<% if (commonData.error) { %>
|
||||
<div id="errorModal">
|
||||
|
||||
12
views/partials/nav.ejs~
Normal file
12
views/partials/nav.ejs~
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- views/partials/nav.ejs -->
|
||||
<aside>
|
||||
<nav class="nav-links">
|
||||
Falls on Fire
|
||||
<ul>
|
||||
<li><a href="/mytickets">View My Tickets</a></li>
|
||||
<li><a href="/camps">View Camps (Admin)</a></li>
|
||||
<li><a href="/settings">Settings (Admin)</a></li>
|
||||
<li><a href="/logout">Log Out</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -28,6 +28,11 @@
|
||||
<input type="email" name="email">
|
||||
<button type="submit">Deactivate</button>
|
||||
</form>
|
||||
<form action="/addopen" method="post">
|
||||
Open Camping Tickets: <%=opencount%> / <%=settings['open-limit']%>
|
||||
<input type="number" name="addopen">
|
||||
<button type="submit">Add</button>
|
||||
</form
|
||||
<form action="/importfb" method="post" enctype="multipart/form-data">
|
||||
Import Tickets (Frostburn Format):
|
||||
<input type="file" name="file">
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Your Tickets</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome, <%= username %>!</h1>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Owner</th>
|
||||
<th>Offered To</th>
|
||||
<th>QRCode</th>
|
||||
</tr>
|
||||
<% for (const t in tlist) { %>
|
||||
<tr>
|
||||
<td> <%= t %> </td>
|
||||
<td> <%= tlist[t].owner %> </td>
|
||||
<td> <%= tlist[t].offered %> </td>
|
||||
<td> <%= simpledata %> </td>
|
||||
<td> <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> </td>
|
||||
</tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Your Tickets</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome, <%= username %>!</h1>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Owner</th>
|
||||
<th>Offered To</th>
|
||||
<th>QRCode</th>
|
||||
</tr>
|
||||
<% for (const t in tlist) { %>
|
||||
<tr>
|
||||
<td> <%= t %> </td>
|
||||
<td> <%= tlist[t].owner %> </td>
|
||||
<td> <%= tlist[t].offered %> </td>
|
||||
<td> <%= simpledata %> </td>
|
||||
<td> <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> </td>
|
||||
</tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user