first drop

This commit is contained in:
cws 2020-02-01 18:40:02 +01:00
parent b27fcef9e7
commit c3376522eb
13 changed files with 383 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
package.json
package-lock.json
assets/js/validate.min.js

View file

@ -1,2 +1,39 @@
# ghost-contact-form # ghost-contact-form
Contact Forms in Ghost — Without External Services Contact Forms in Ghost — Without External Services
## Prerequisites
- A supported version of Node.js
- npm to manage packages
## Quickstart Install
```
$ git clone https://github.com/styxlab/ghost-contact-form.git
$ cd ghost-contact-form
$ sh install.sh
```
## Configure
Adapt the `.env` file to your needs. The following variables must be defined.
```
SMTP_HOST = mail.server.com
SMTP_USER = user@server.com
SMTP_PASS = strong password
ALLOW_ORIGIN = https://your-blog.com
EMAIL_FROM = noreply@your-blog.com
EMAIL_TO = your@email.com
```
## Usage
```
$ node ghost-contact-svc.js
```
## Test Locally
```
$ http://lcoalhost:7000/v1/demo
```

53
assets/css/demo.css Normal file
View file

@ -0,0 +1,53 @@
body {
margin: 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
background-color: #fff;
}
h1 {
margin: 10px 0;
font-family: inherit;
font-weight: bold;
line-height: 20px;
color: inherit;
text-rendering: optimizelegibility;
}
h1 small {
font-weight: normal;
line-height: 1;
color: #999;
}
h1 {
line-height: 40px;
}
h1 {
font-size: 38.5px;
}
h1 small {
font-size: 24.5px;
}
body {
margin-top: 90px;
}
.header {
position: fixed;
top: 0;
left: 50%;
margin-left: -480px;
background-color: #fff;
border-bottom: 1px solid #ddd;
padding-top: 10px;
z-index: 10;
}
.footer {
color: #ddd;
font-size: 12px;
text-align: center;
margin-top: 20px;
}
.footer a {
color: #ccc;
text-decoration: underline;
}

56
assets/css/form.css Normal file
View file

@ -0,0 +1,56 @@
form,input,select,textarea,button {
font-family: avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif;
display: block;
margin: 0 0 2rem 0;
padding: 1rem 0;
width: 100% !important;
align-items: flex-start;
box-sizing: border-box;
}
input,select,textarea {
color: darkslategray;
background-color: white;
background-clip: padding-box;
line-height: 1.5;
border-radius: .5rem;
padding: 1rem 4rem;
border: 1px solid #ced4da;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
}
select:required:invalid {
color: silver;
}
input:focus, select:focus, textarea:focus {
border-color: royalblue;
box-shadow: none;
-webkit-box-shadow: none;
}
button {
margin-bottom: 0;
color: white;
background-color: royalblue;
border-color: royalblue;
user-select: none;
line-height: 1.5;
border-radius: .5rem;
border: 1px solid transparent;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
button:hover {
filter: brightness(80%);
}
input:placeholder {
color: silver;
}
option[value=""][disabled] {
display: none;
color: silver;
}
option {
color: black;
}

View file

@ -0,0 +1,89 @@
var formProcessor = (function(){
"use strict";
var constraints = {
name: {
presence: true,
length: {
minimum: 2,
maximum: 30,
message: "must be longer."
}
},
email: {
presence: true,
email: true,
},
subject: {
presence: true,
length: {
minimum: 1,
message: "must be selected."
}
},
message: {
presence: true,
length: {
minimum: 10,
maxumum: 4000,
message: "must be longer."
}
},
robot: {
presence: {
allowEmpty: true
},
length: {
is: 0,
message: "must be filled out."
}
}
};
function formAlert(text) {
document.getElementById("responsemsg").innerHTML = "<br><p><em>" + text + "</em></p>";
};
function responseAlert(text) {
document.getElementById("serverresponse").innerHTML = "<br><p><em>" + text + "</em></p>";
};
function sendData(data, url) {
formAlert("One second...");
var postURL = (url || "https://api.your-blog.com/v1/contact");
var http = new XMLHttpRequest();
http.open("POST", postURL, true);
http.setRequestHeader("Content-Type", "application/json");
data.source_url = window.location.href;
http.send(JSON.stringify(data));
http.onload = function() {
formAlert("Thank you, your message has been sent!");
if(url != undefined){
responseAlert("Only the demo should display the server response: " + http.responseText);
}
document.getElementById("contact-form").reset();
}
};
return ({
process: function(url) {
var attributes = {
name: document.forms["contact-form"]["name"].value,
email: document.forms["contact-form"]["email"].value,
subject: document.forms["contact-form"]["subject"].value,
message: document.forms["contact-form"]["message"].value,
robot: document.forms["contact-form"]["_norobots"].value
};
validate.async(attributes, constraints)
.then(function(success) {
//console.log("Success", success);
sendData(success, url);
})
.catch(function(error) {
//console.log("ValidationError", error);
formAlert(Object.values(error)[0][0]);
})
}
});
}());

35
demo.html Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link type="text/css" rel="stylesheet" href="/v1/assets/css/demo.css">
<link type="text/css" rel="stylesheet" href="/v1/assets/css/form.css">
<script src="/v1/assets/js/validate.min.js" type="text/javascript"></script>
<script src="/v1/assets/js/formProcessor.js" type="text/javascript"></script>
</head>
<body>
<div class="container header">
<h1>ghost-contact-form <small>demo</small></h1>
<form id="contact-form" method="post" data-format="inline">
<input type="text" id="name" placeholder="Name" pattern=".{2,30}" required>
<input type="email" id="email" placeholder="Your email address" required>
<select id="subject" pattern=".{1,20}" required>
<option value="" disabled selected>Pick one subject...</option>
<option value="comment">I want to leave a comment</option>
<option value="contribution">I want to contribute</option>
</select>
<textarea rows="5" id="message" placeholder="Message" pattern=".{10,4000}" required></textarea>
<input type="text" name="_norobots" style="display:none !important;">
<button class="btn" type="submit" id="submit" value="Send" onclick="formProcessor.process('/v1/contact');return false;">Send</button>
<span id="responsemsg"></span>
<span id="serverresponse" style="color: red;"></span>
</form>
<div class="container footer">Generated by <a href="https://github.com/styxlab/ghost-contact-form">styxlab/ghost-contact-form</a></div>
</div>
</body>
</html>

View file

@ -0,0 +1,2 @@
<script src="https://api.your-blog.com/v1/assets/js/validate.min.js" type="text/javascript"></script>
<script src="https://api.your-blog.com/v1/assets/js/formProcessor.js" type="text/javascript"></script>

View file

@ -0,0 +1 @@
<link type="text/css" rel="stylesheet" href="https://api.your-blog.com/v1/assets/css/form.css">

View file

@ -0,0 +1,13 @@
<form id="contact-form" method="post" data-format="inline">
<input type="text" id="name" placeholder="Name" pattern=".{2,30}" required>
<input type="email" id="email" placeholder="Your email address" required>
<select id="subject" pattern=".{1,20}" required>
<option value="" disabled selected>Pick one subject...</option>
<option value="comment">I want to leave a comment</option>
<option value="contribution">I want to contribute</option>
</select>
<textarea rows="5" id="message" placeholder="Message" pattern=".{10,4000}" required></textarea>
<input type="text" name="_norobots" style="display:none !important;">
<button class="btn" type="submit" id="submit" value="Send" onclick="formProcessor.process();return false;">Send</button>
<span id="responsemsg"></span>
</form>

58
ghost-contact-svc.js Normal file
View file

@ -0,0 +1,58 @@
'use strict';
var express = require('express');
var cors = require('cors');
var bodyParser = require("body-parser");
var dotenv = require('dotenv').config();
var nodemailer = require('nodemailer');
var smtpTrans = require('nodemailer-smtp-transport');
var validator = require("email-validator");
var sanitize = require('sanitize-html');
var smtp = { "auth": {}, "port": 465, "secure": true, "tls": {"rejectUnauthorized": false}, "debug": false};
smtp.host = process.env.SMTP_HOST;
smtp.auth.user = process.env.SMTP_USER;
smtp.auth.pass = process.env.SMTP_PASS;
var transporter = nodemailer.createTransport(smtpTrans(smtp));
var app = express();
app.disable('x-powered-by');
app.use(bodyParser.urlencoded({limit: '1mb', extended: false}));
app.use(bodyParser.json({limit: '1mb'}));
app.use(cors({origin: process.env.ALLOW_ORIGIN,
allowedHeaders: ['Content-Type', 'application/json; charset=utf-8', 'text/html; charset=utf-8']}));
app.use('/v1/assets', express.static(__dirname + '/assets'));
app.post('/v1/contact', function(req, res) {
if(validator.validate(req.body.email)) return sendEmail(req.body, res);
res.status(403).json({"validation": "no email"});
});
app.get('/v1/demo', function(req, res) {
res.sendFile(__dirname + '/demo.html');
});
app.listen(process.env.PORT || 7000, function(){
console.log('Listening on http://localhost:' + (process.env.PORT || 7000));
});
function sendEmail(data, res){
var email = { "from": process.env.EMAIL_FROM, "to": process.env.EMAIL_TO};
email.subject = 'MY BLOG - ' + (data.subject && data.subject.toUpperCase());
const output = `
<p>Hello,<p>
<p>You got a new contact request.</p>
<h3>Contact Details</h3>
<ul><li>Name: ${data.name}</li><li>Email: ${data.email}</li></ul>
<h3>Message:</h3>
<p>${data.message}</p>
`
email.html = sanitize(output, {
allowedTags: sanitize.defaults.allowedTags.concat([ 'img' ])
});
transporter.sendMail(email, function(error, info){
if(error) return res.json({"sendEmail": "failed"});
res.json({"sendEmail": "ok"});
});
};

14
ghost-contact.service Normal file
View file

@ -0,0 +1,14 @@
[Unit]
Description=ghost-contact-svc microservice
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=joe
ExecStart=/usr/bin/env node /home/joe/ghost-contact-form/ghost-contact-svc.js
[Install]
WantedBy=multi-user.target

13
install.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
sudo npm -g install depcheck
npm -y init
deps=$(depcheck |cut -d: -f1 |tr -d '* ' | tail -n +2)
for package in ${deps[@]}; do
echo 'Installing package: ' ${package}
npm install ${package} --save
done
npm install validate.js --save
cp node_modules/validate.js/validate.min.js assets/js

8
test.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
contact="https://api.your-server.com/v1/contact"
curl -v -X POST ${contact} -H "Content-Type: application/json" \
-d '{"email": "test@example.com", "name": "John Izzo", "subject": "feedback", "message": "Production Light!"}'