diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/css/mobile-styles.css | 23 | ||||
-rw-r--r-- | src/css/styles.css | 243 | ||||
-rw-r--r-- | src/html/404.html | 22 | ||||
-rw-r--r-- | src/html/email-confirm.html | 22 | ||||
-rw-r--r-- | src/html/email-success.html | 22 | ||||
-rw-r--r-- | src/html/email-unsubscribe.html | 22 | ||||
-rw-r--r-- | src/html/email.html | 28 | ||||
-rw-r--r-- | src/index.js | 61 | ||||
-rw-r--r-- | src/server.js | 51 |
9 files changed, 494 insertions, 0 deletions
diff --git a/src/css/mobile-styles.css b/src/css/mobile-styles.css new file mode 100644 index 0000000..36553cc --- /dev/null +++ b/src/css/mobile-styles.css @@ -0,0 +1,23 @@ +@media screen and (min-width: 900px) { + body { + width: 100%; + /* margin: auto; */ + } + .card-img { + display: flex; + flex-flow: row wrap; + align-content: flex-start; + } + .card img { + width: 400px; + } + form input[type=text], input[type=password], input[type=email] { + max-width: 600px; + } + form textarea { + max-width: 600px; + } + form input[type=submit] { + margin-left: 0px; + } +}
\ No newline at end of file diff --git a/src/css/styles.css b/src/css/styles.css new file mode 100644 index 0000000..b89a373 --- /dev/null +++ b/src/css/styles.css @@ -0,0 +1,243 @@ +:root { + --background: #fff; + --background-accent: #f0f0f0; + --accent: #ada; + --text: #000; + --btn-color: #2d2; + --btn-text: #000; + --btn-hover: #0a0; + --light-text: #aaa; +} + +body { + width: 60%; + min-width: 500px; + margin: auto; + /* margin: 0px; */ + background-color: var(--background); + font-family: Arial, Helvetica, sans-serif; + height: 100%; + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +h1, h2 { + font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; + font-weight: 150; +} + +h1 { + font-size: 30pt; + margin: auto; + margin-bottom: 0; + margin-left: 20px; +} + +.card-title { + margin-left: 0; +} + +@keyframes zoom-left { + 0% {left: 100%; } + 100% {left: 0;} +} + +footer { + text-align: center; + padding-top: 1em; + padding-bottom: 2em; + border-top: 1px solid var(--light-text); + color: var(--light-text); + background-color: var(--background-accent); +} + +footer a { + color: var(--light-text); + text-decoration: underline; +} + +.essay{ + width: 70%; +} + +.figure { + border: 1px solid #777; + background-color: #ddd; + padding: 10px; +} + +img { + image-orientation: from-image; +} + +a { + text-decoration: none; +} + +.card-title { + +} + +.card { + margin: 1em; + padding: 2%; + border: 1px solid var(--text); + border-radius: 3px; + display: inline; + min-width: 200px; + background-color: var(--background-accent); +} + +.card img { + width: 100%; + margin: 2%; + border: 2px solid #888; +} +.spacer { + width: 1em; + display: inline-block; +} +#feed { + width: 100%; +} +.feed { + /* width: 80%; */ + min-width: 200px; + margin: auto; + display: flex; + flex-direction: column; + margin-top: 8px; + animation-name: zoom-left; + animation-duration: .5s; + animation-iteration-count: 1; + position: relative; +} + +p { + margin-top: 0; +} + +.titlebar { + padding: 5px; + border-top: 1px solid var(--light-text); + border-bottom: 1px solid var(--light-text); + background-color: var(--background-accent); + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; +} + + +.titlebar a{ + margin: auto; + max-width: 10%; + min-width: 100px; + flex-grow: 1; +} + +.btn { + display: inline-block; + padding: 1px 3px; + border: 1px solid var(--btn-text); + border-radius: 3px; + max-width: 100px; + line-height: 2em; + text-align: center; + vertical-align: middle; + white-space: nowrap; + text-decoration: none; + cursor: pointer; +} + +.btn-primary { + width: 100%; + height: 2em; + font-size: 20px; + background-color: var(--btn-color); + color: var(--btn-text); +} + +.btn-primary:hover{ + background-color: var(--btn-hover); +} + +.btn-secondary { + background-color: var(--btn-color); + color: var(--btn-text); +} + +.btn-secondary:hover{ + background-color: var(--btn-hover); +} + +.date { + font-style: italic; + margin: 0em; +} +.tag { + font-style: normal; + padding-left: 1em; + font-weight: lighter; + font-size: .8em; + text-decoration: none; + color: var(--light-text); +} +.tag::before{ + content: "#"; +} + +.url-table, .ip-table { + width: 30%; +} + +form { + width: 85%; +} + +input[type=text], input[type=password], input[type=email] { + height:1.5em; + width: 100%; + display: block; + margin-top: 5px; +} + +input[type=submit] { + height:2em; + width: 50%; + margin-top: 5px; + margin-left: 25%; +} + +td { + padding-right: 10px; +} + +textarea { + border-radius: 4px; + border: 2px solid var(--background-accent); + width: 100%; + height: 10em; +} + +.feed h1 { + font-size: 24pt; +} + +a.navigation:visited, a.navigation:link { + color: var(--accent); + text-decoration: none; +} + +.email::after { + content: "@marks.kitchen"; +} + +.cool { + float: right; +} + +dl > dt { + font-weight: bold; +} + diff --git a/src/html/404.html b/src/html/404.html new file mode 100644 index 0000000..8ef209d --- /dev/null +++ b/src/html/404.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + +<head> + <title>Mark's Kitchen - Not found</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" type="text/css" href="/css/styles.css"> + <meta name="description" content="Page not found"> +</head> + +<body> + <div> + <h1> + <a class="navigation" href="/" title="marks.kitchen"><</a> + 404 Error + </h1> + <p>That page doesn't seem to exist...</p> + </div> +</body> + +</html> diff --git a/src/html/email-confirm.html b/src/html/email-confirm.html new file mode 100644 index 0000000..3fc6e1a --- /dev/null +++ b/src/html/email-confirm.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + +<head> + <title>Mark's Kitchen - Email</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" type="text/css" href="/css/styles.css"> + <meta name="description" content="Email unsubscribe"> +</head> + +<body> + <div> + <h1> + Unsubcribe? + </h1> + <a href="confirm">Click to unsubscribe!</a> + </div> + </div> +</body> + +</html> diff --git a/src/html/email-success.html b/src/html/email-success.html new file mode 100644 index 0000000..c191a7a --- /dev/null +++ b/src/html/email-success.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + +<head> + <title>Mark's Kitchen - Email</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" type="text/css" href="/css/styles.css"> + <meta name="description" content="Email sucess"> +</head> + +<body> + <div> + <h1> + Email + </h1> + <h2 id="status">Subscribed, thank you!</h2> + </div> + </div> +</body> + +</html> diff --git a/src/html/email-unsubscribe.html b/src/html/email-unsubscribe.html new file mode 100644 index 0000000..a8fdd1a --- /dev/null +++ b/src/html/email-unsubscribe.html @@ -0,0 +1,22 @@ +<!doctype html> +<html lang="en"> + +<head> + <title>Mark's Kitchen - Email</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" type="text/css" href="/css/styles.css"> + <meta name="description" content="Email unsubscrube"> +</head> + +<body> + <div> + <h1> + Email + </h1> + <h2 id="status">You are now unsubscribed!</h2> + </div> + </div> +</body> + +</html> diff --git a/src/html/email.html b/src/html/email.html new file mode 100644 index 0000000..e957b90 --- /dev/null +++ b/src/html/email.html @@ -0,0 +1,28 @@ +<!doctype html> +<html lang="en"> + +<head> + <title>Mark's Kitchen - Email</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" type="text/css" href="/css/styles.css"> + <meta name="description" content="Sign up for emails"> +</head> + +<body> + <div> + <h1> + Email + </h1> + <p>Sign up to receive Mark's email newsletter.</p> + <div class="form"> + <form action="/" method="post" enctype="application/x-www-form-urlencoded"> + <input type="text" placeholder="Your name" name="name" required> + <input type="email" placeholder="Your email" name="email" required> + <input type="submit" value="Submit"> + </form> + </div> + </div> +</body> + +</html> diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..dbef725 --- /dev/null +++ b/src/index.js @@ -0,0 +1,61 @@ +const server = require('./server'); +const Sequelize = require('sequelize'); +const fs = require('fs'); +const path = require('path'); +const jwt = require('jsonwebtoken'); + +const config = JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'))); + +const dbCreds = config.database; +const secret = config.jwt_secret; + +const jwtFunctions = { + sign: function(message) { + return jwt.sign({ value: message }, secret); + }, + verify: function(token) { + return jwt.verify(token, secret).value; + } +} + +const database = new Sequelize(dbCreds.database, dbCreds.user, dbCreds.password, { + logging(str) { + console.debug(`DB:${str}`); + }, + dialectOptions: { + charset: 'utf8mb4', + multipleStatements: true, + }, +// host: dbCreds.host, + dialect: 'mysql', + pool: { + max: 5, + min: 0, + idle: 10000, + }, +}); + +database.authenticate().then(() => { + console.debug(`database connection successful: ${dbCreds.database}`); +}, (e) => console.log(e)); + +async function sync(alter, force, callback) { + await database.sync({ alter, force, logging: console.log }); +} + +function setUpModels(){ + const models = { + "emails": database.define('email', { + address: Sequelize.STRING, + name: Sequelize.STRING, + code: Sequelize.STRING, + }), + } + return models; +} + +const models = setUpModels(); +sync(); +server.setUpRoutes(models, jwtFunctions, database); +server.listen(config.port); + diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..7192f49 --- /dev/null +++ b/src/server.js @@ -0,0 +1,51 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const crypto = require('crypto'); +const cache = require('apicache').middleware; + +const server = express(); +server.use(bodyParser.urlencoded({ extended: true })); + +function listen(port) { + server.listen(port, () => console.info(`Listening on port ${port}!`)); +} + +function setUpRoutes(models, jwtFunctions, database) { + server.get('/', cache('5 minutes'), (req, res) => res.sendFile(__dirname + "/html/email.html")) + server.get('/success', cache('5 minutes'), (req, res) => res.sendFile(__dirname + "/html/email-success.html")) + server.get('/unsubscribe', cache('5 minutes'), (req, res) => res.sendFile(__dirname + "/html/email-unsubscribe.html")) + server.post('/', async (req, res, next) => { + const name = req.body.name; + const email = req.body.email; + if (name && email) { + const code = crypto.randomBytes(40).toString('hex').slice(0, 40) + models.emails.create({"name": name, "address": email, "code": code}) + res.redirect('/success'); + } else { + console.debug("Error with email submission") + } + }) + server.get('/unsubscribe/:code/check', async (req, res, next) => { + res.sendFile(__dirname + "/html/email-confirm.html") + }) + server.get('/unsubscribe/:code/confirm', async (req, res, next) => { + await models.emails.destroy({ where: {"code": req.params.code}}) + res.redirect('/unsubscribe'); + }) + server.get('/css/:id', cache('5 minutes'), (req, res) => { + res.sendFile(__dirname + "/css/" + req.params.id); + }); + + + // Final 404 fallback + server.use(function(req, res) { + res.status(400); + res.sendFile(__dirname + "/html/404.html"); + }); +} + +module.exports = { + listen, + setUpRoutes +}; + |