aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/mobile-styles.css23
-rw-r--r--src/css/styles.css243
-rw-r--r--src/html/404.html22
-rw-r--r--src/html/email-confirm.html22
-rw-r--r--src/html/email-success.html22
-rw-r--r--src/html/email-unsubscribe.html22
-rw-r--r--src/html/email.html28
-rw-r--r--src/index.js61
-rw-r--r--src/server.js51
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">&lt;</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
+};
+