generated from corysanin/nodejs-web-template
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c86afff89c | |||
| 7582d1054e | |||
| 77ea3018c1 | |||
| f5c9d8c0c4 | |||
| 1a73732f7c | |||
| cf43cdcf22 | |||
| 4dbf6a4ae6 | |||
| 617cffc052 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,5 +7,10 @@ services:
|
|||||||
context: ./
|
context: ./
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
environment:
|
||||||
|
ADMINPARAM: password
|
||||||
|
ADMINPASS: letmein
|
||||||
|
volumes:
|
||||||
|
- ./config/:/usr/src/app/config/
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "madisonlinux",
|
"name": "madisonlinux",
|
||||||
"version": "0.0.3",
|
"version": "0.0.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "madisonlinux",
|
"name": "madisonlinux",
|
||||||
"version": "0.0.3",
|
"version": "0.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^2.2.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
"ics": "^3.8.1",
|
"ics": "^3.8.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "madisonlinux",
|
"name": "madisonlinux",
|
||||||
"version": "0.0.3",
|
"version": "0.0.7",
|
||||||
"description": "Website for upcoming Linux install party in Madison WI",
|
"description": "Website for upcoming Linux install party in Madison WI",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"web",
|
"web",
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"views"
|
"views"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^2.2.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
"ics": "^3.8.1",
|
"ics": "^3.8.1",
|
||||||
|
|||||||
28
scripts/form.js
Normal file
28
scripts/form.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const submit = document.getElementById('submitbtn');
|
||||||
|
submit.addEventListener('click', async () => {
|
||||||
|
submit.disabled = true;
|
||||||
|
const action = submit.parentElement.action;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
os: document.getElementById('os-select').value,
|
||||||
|
form: document.getElementById('form-select').value
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(`Response status: ${resp.status}`);
|
||||||
|
}
|
||||||
|
location.reload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
64
src/Web.ts
64
src/Web.ts
@@ -3,12 +3,19 @@ import crypto from 'crypto';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import type { Express } from 'express';
|
import type { Express } from 'express';
|
||||||
import { createEvent} from 'ics';
|
import { createEvent } from 'ics';
|
||||||
|
import path from 'path';
|
||||||
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
interface WebConfig {
|
interface WebConfig {
|
||||||
port?: number;
|
port?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Install {
|
||||||
|
os: 'manjaro' | 'mint' | 'chrome' | 'other',
|
||||||
|
form: 'laptop' | 'desktop' | 'aio'
|
||||||
|
}
|
||||||
|
|
||||||
const DATE = process.env['DATE'] || 'November 1st';
|
const DATE = process.env['DATE'] || 'November 1st';
|
||||||
const TIME = process.env['TIME'] || '2:30PM-5:30PM';
|
const TIME = process.env['TIME'] || '2:30PM-5:30PM';
|
||||||
|
|
||||||
@@ -23,6 +30,7 @@ class Web {
|
|||||||
private _webserver: http.Server | null = null;
|
private _webserver: http.Server | null = null;
|
||||||
private app: Express | null = null;
|
private app: Express | null = null;
|
||||||
private port: number;
|
private port: number;
|
||||||
|
private installs: Install[] = [];
|
||||||
// private options: WebConfig;
|
// private options: WebConfig;
|
||||||
|
|
||||||
constructor(options: WebConfig = {}) {
|
constructor(options: WebConfig = {}) {
|
||||||
@@ -33,13 +41,21 @@ class Web {
|
|||||||
initialize = async () => {
|
initialize = async () => {
|
||||||
// const options = this.options;
|
// const options = this.options;
|
||||||
const app: Express = this.app = express();
|
const app: Express = this.app = express();
|
||||||
|
const installPath = process.env['installstat'] || process.env['INSTALLSTAT'] || path.join(process.cwd(), 'config', 'installs.json');
|
||||||
|
try {
|
||||||
|
this.installs = JSON.parse(await fsp.readFile(installPath, 'utf-8'));
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
this.installs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', 1);
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('view options', { outputFunctionName: 'echo' });
|
app.set('view options', { outputFunctionName: 'echo' });
|
||||||
app.use('/assets', express.static('assets', { maxAge: '30 days' }));
|
app.use('/assets', express.static('assets', { maxAge: '30 days' }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
|
||||||
app.use((_req, res, next) => {
|
app.use((_req, res, next) => {
|
||||||
crypto.randomBytes(32, (err, randomBytes) => {
|
crypto.randomBytes(32, (err, randomBytes) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -56,7 +72,17 @@ class Web {
|
|||||||
res.send('Healthy');
|
res.send('Healthy');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', (_, res) => {
|
const adminParam = process.env['ADMINPARAM'];
|
||||||
|
const adminPass = process.env['ADMINPASS'];
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
const adminMode = adminParam && adminPass && adminParam in req.query && req.query[adminParam] === adminPass;
|
||||||
|
if (req.query?.['utm_medium']) {
|
||||||
|
console.log(`${req.query['utm_medium']} | ${req.headers?.['user-agent']}`);
|
||||||
|
}
|
||||||
|
else if(!adminMode){
|
||||||
|
console.log(req.headers?.['user-agent']);
|
||||||
|
}
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
page: {
|
page: {
|
||||||
title: 'Madison End of 10 Install Party',
|
title: 'Madison End of 10 Install Party',
|
||||||
@@ -64,10 +90,29 @@ class Web {
|
|||||||
description: `Windows 10 support is ending, but you may not need a brand-new PC! Sector67 in Madison, Wisconsin is hosting a Linux install party to help the community keep their current computers usable and up-to-date. Join us on ${DATE}`
|
description: `Windows 10 support is ending, but you may not need a brand-new PC! Sector67 in Madison, Wisconsin is hosting a Linux install party to help the community keep their current computers usable and up-to-date. Join us on ${DATE}`
|
||||||
},
|
},
|
||||||
date: DATE,
|
date: DATE,
|
||||||
time: TIME
|
installs: this.installs,
|
||||||
|
time: TIME,
|
||||||
|
adminMode
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/', async (req, res) => {
|
||||||
|
const adminMode = adminParam && adminPass && adminParam in req.query && req.query[adminParam] === adminPass;
|
||||||
|
if (!adminMode) {
|
||||||
|
res.redirect('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const submit = req.body;
|
||||||
|
if (!('os' in submit && 'form' in submit)) {
|
||||||
|
res.status(400).send('bad request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.installs.push(submit);
|
||||||
|
console.log(`updating ${installPath}`);
|
||||||
|
await fsp.writeFile(installPath, JSON.stringify(this.installs, null, 2));
|
||||||
|
res.send('ok');
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/os', (_, res) => {
|
app.get('/os', (_, res) => {
|
||||||
res.render('os', {
|
res.render('os', {
|
||||||
page: {
|
page: {
|
||||||
@@ -78,6 +123,10 @@ class Web {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/firefox', (_, res) => {
|
||||||
|
res.redirect('https://gist.github.com/CorySanin/d322dc6ebe534c23ba183920d154e566');
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/event.ics', (_, res) => {
|
app.get('/event.ics', (_, res) => {
|
||||||
createEvent({
|
createEvent({
|
||||||
uid: '1@madisonlinux.com',
|
uid: '1@madisonlinux.com',
|
||||||
@@ -93,7 +142,7 @@ class Web {
|
|||||||
organizer: { name: 'Cory Sanin', email: 'endof10@cory.sanin.dev' },
|
organizer: { name: 'Cory Sanin', email: 'endof10@cory.sanin.dev' },
|
||||||
method: 'REQUEST'
|
method: 'REQUEST'
|
||||||
}, (err, s) => {
|
}, (err, s) => {
|
||||||
if(err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).send('something went wrong.');
|
res.status(500).send('something went wrong.');
|
||||||
return;
|
return;
|
||||||
@@ -105,6 +154,11 @@ class Web {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use(function (req, res, _) {
|
||||||
|
console.log(`404: ${req.url} requested by ${req.ip} "${req.headers['user-agent']}"`);
|
||||||
|
res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
this._webserver = this.app.listen(this.port, () => console.log(`madisonlinux is running on port ${this.port}`));
|
this._webserver = this.app.listen(this.port, () => console.log(`madisonlinux is running on port ${this.port}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,34 @@ img {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#installs {
|
||||||
|
h1 {
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: zoomie;
|
||||||
|
animation-duration: 1.5s;
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: .05em .25em;
|
||||||
|
border-radius: .15em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomie {
|
||||||
|
0% {
|
||||||
|
transform: perspective(800px) translate3d(0, 0, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: perspective(800px) translate3d(0, 0, 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: perspective(800px) translate3d(0, 0, 0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width:540px) {
|
@media screen and (max-width:540px) {
|
||||||
.content {
|
.content {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
|
|||||||
@@ -44,4 +44,8 @@ a.btn {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#installs span {
|
||||||
|
background: #1A237E;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<link rel="canonical" href="<%= page.canonical%>"/>
|
<link rel="canonical" href="<%= page.canonical%>"/>
|
||||||
<% } %>
|
<% } %>
|
||||||
<link rel="shortcut icon" href="/assets/svg/favicon.svg">
|
<link rel="shortcut icon" href="/assets/svg/favicon.svg">
|
||||||
<link rel="stylesheet" href="/assets/css/styles.css?v5">
|
<link rel="stylesheet" href="/assets/css/styles.css?v6">
|
||||||
<script nonce="<%= cspNonce %>">
|
<script nonce="<%= cspNonce %>">
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
document.body.classList.remove('preload');
|
document.body.classList.remove('preload');
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
<body class="preload">
|
<body class="preload">
|
||||||
<%- include("navigation", locals) %>
|
<%- include("navigation", locals) %>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<% if (adminMode) { %>
|
||||||
|
<%- include("installform", locals) %>
|
||||||
|
<% } %>
|
||||||
|
<% if (installs && installs.length) { %>
|
||||||
|
<div id="installs">
|
||||||
|
<h1>We have saved <span><%= installs.length %></span> computer<% if (installs.length > 1) { %>s<% } %>!</h1>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<p><a href="https://www.sector67.org/">Sector67</a> presents...</p>
|
<p><a href="https://www.sector67.org/">Sector67</a> presents...</p>
|
||||||
<h1>Linux Install Party Madison</h1>
|
<h1>Linux Install Party Madison</h1>
|
||||||
<h2>Are you ready to reduce e-waste and save some money?</h2>
|
<h2>Are you ready to reduce e-waste and save some money?</h2>
|
||||||
|
|||||||
19
views/installform.ejs
Normal file
19
views/installform.ejs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<div>
|
||||||
|
<select name="OS" id="os-select">
|
||||||
|
<option value="mint">Mint</option>
|
||||||
|
<option value="manjaro">Manjaro</option>
|
||||||
|
<option value="chrome">ChromeOS</option>
|
||||||
|
<option value="other">Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<select name="form" id="form-select">
|
||||||
|
<option value="laptop">Laptop</option>
|
||||||
|
<option value="desktop">Desktop</option>
|
||||||
|
<option value="aio">AIO</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
<input type="button" value="Submit" id="submitbtn" />
|
||||||
|
</form>
|
||||||
|
<script src="/assets/js/form.js?v1" nonce="<%= cspNonce %>"></script>
|
||||||
@@ -35,6 +35,13 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<h2>Gaming Focused</h2>
|
<h2>Gaming Focused</h2>
|
||||||
|
<p>
|
||||||
|
Before migrating from Windows to Linux, please consult <a href="https://www.protondb.com/">ProtonDB</a> to verify
|
||||||
|
the games you want to play run in Linux. If you play competitive games that utilize anti-cheat,
|
||||||
|
<a href="https://areweanticheatyet.com/">Are We Anti-Cheat Yet?</a> is also a handy resource. Compatibilty is quite
|
||||||
|
strong these days, but since it's not 100% it's better to know ahead than to be blindsided when your favorite games
|
||||||
|
won't start up.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
For gaming-focused PC's, of course most general purpose distros will do. However, there are a couple
|
For gaming-focused PC's, of course most general purpose distros will do. However, there are a couple
|
||||||
options worth your consideration.
|
options worth your consideration.
|
||||||
|
|||||||
Reference in New Issue
Block a user