From dc18c389ac0f545f781b1445771abbfa2224a6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AA=E0=AE=BE=E0=AE=B2=E0=AE=BE=E0=AE=9C=E0=AE=BF?= Date: Sat, 17 Jun 2017 19:32:59 +0530 Subject: [PATCH] Fixes #132: option added to push documentation to GH-PAGES (#133) --- .gitignore | 2 ++ app.js | 34 +++++++++++++++++++++++++ backend/generator.js | 7 +++--- backend/ghdeploy.js | 37 +++++++++++++++++++++++++++ package.json | 4 +++ public/scripts/form.js | 4 ++- public/stylesheets/style.css | 8 +++++- publish_docs.sh | 48 ++++++++++++++++++++++++++++++++---- routes/index.js | 31 ++++++++++++++++++++++- util/crypter.js | 30 ++++++++++++++++++++++ views/deploy.jade | 32 ++++++++++++++++++++++++ views/index.jade | 1 + 12 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 backend/ghdeploy.js create mode 100644 util/crypter.js create mode 100644 views/deploy.jade diff --git a/.gitignore b/.gitignore index 03f27ddd..1d909df2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ temp/ .idea/ +.env +npm-debug.log diff --git a/app.js b/app.js index ee2d023a..1e24c2c4 100644 --- a/app.js +++ b/app.js @@ -4,11 +4,17 @@ var favicon = require("serve-favicon"); var logger = require("morgan"); var cookieParser = require("cookie-parser"); var bodyParser = require("body-parser"); +var passport = require("passport"); +var githubStrategy = require("passport-github").Strategy; +var dotenv = require("dotenv"); +var session = require("express-session"); +dotenv.config({path: './.env'}) /** * Backend Scripts */ var generator = require("./backend/generator"); +var ghdeploy = require("./backend/ghdeploy") var app = express(); @@ -32,8 +38,33 @@ app.use(logger("dev")); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); +app.use(session({ + secret: process.env.SECRET, + resave: true, + saveUninitialized: true, + store: new session.MemoryStore() +})); +app.use(passport.initialize()); +app.use(passport.session()); app.use(express.static(path.join(__dirname, "public"))); +passport.use(new githubStrategy({ + clientID: process.env.CLIENTID, + clientSecret: process.env.CLIENTSECRET, + callbackURL: process.env.CALLBACKURL +}, function (accessToken, refreshToken, profile, cb) { + profile.token = accessToken; + cb(null, profile) +})) + +passport.serializeUser(function(user, cb) { + cb(null, user); +}); + +passport.deserializeUser(function(obj, cb) { + cb(null, obj); +}); + app.use("/preview", express.static(path.join(__dirname, "temp"))) app.use("/", index); @@ -45,6 +76,9 @@ io.on('connection', function(socket){ socket.on('execute', function (formData) { generator.executeScript(socket, formData); }); + socket.on('deploy', function (data) { + ghdeploy.deployPages(socket, data); + }); }); // catch 404 and forward to error handler diff --git a/backend/generator.js b/backend/generator.js index 0249f704..51196a94 100644 --- a/backend/generator.js +++ b/backend/generator.js @@ -2,14 +2,13 @@ var exports = module.exports = {}; var uuidV4 = require("uuid/v4"); var validation = require("../public/scripts/validation.js"); +var spawn = require('child_process').spawn; exports.executeScript = function (socket, formData) { if (!validation.isValid(formData)) { socket.emit('err-logs', "Failed to generated documentation due to an error in input fields"); return false; } - var spawn = require('child_process').spawn; - var email = formData.email; var gitUrl = formData.gitUrl; var docTheme = formData.docTheme; @@ -41,10 +40,10 @@ exports.executeScript = function (socket, formData) { process.on('exit', function (code) { console.log('child process exited with code ' + code); if (code === 0) { - socket.emit('success', {email: email, uniqueId: uniqueId}); + socket.emit('success', {email: email, uniqueId: uniqueId, gitUrl: gitUrl}); } else { socket.emit('failure', {errorCode: code}); } }); return true; -}; \ No newline at end of file +}; diff --git a/backend/ghdeploy.js b/backend/ghdeploy.js new file mode 100644 index 00000000..c26f86ca --- /dev/null +++ b/backend/ghdeploy.js @@ -0,0 +1,37 @@ +var crypter = require("../util/crypter.js") +var spawn = require('child_process').spawn; + +exports.deployPages = function (socket, data) { + var donePercent = 0; + var repoName = data.gitURL.split("/")[4].split(".")[0]; + var webUI = "true"; + var username = data.username + var oauthToken = crypter.decrypt(data.encryptedToken) + const args = [ + "-e", data.email, + "-i", data.uniqueId, + "-w", webUI, + "-n", username, + "-o", oauthToken, + "-r", repoName + ]; + var process = spawn("./publish_docs.sh", args); + + process.stdout.on('data', function (data) { + console.log(data.toString()); + socket.emit('deploy-logs', {donePercent: donePercent, data: data.toString()}); + donePercent += 18; + }); + + process.stderr.on('data', function (data) { + console.log(data.toString()); + socket.emit('err-logs', data.toString()); + }); + + process.on('exit', function (code) { + console.log('child process exited with code ' + code); + if (code === 0) { + socket.emit('deploy-success', {pagesURL: "https://" + data.username + ".github.io/" + repoName}); + } + }); +} diff --git a/package.json b/package.json index 55eaf01f..fb33c990 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,13 @@ "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", + "dotenv": "^4.0.0", "express": "~4.15.2", + "express-session": "^1.15.3", "jade": "~1.11.0", "morgan": "~1.8.1", + "passport": "^0.3.2", + "passport-github": "^1.1.0", "serve-favicon": "~2.4.2", "socket.io": "^2.0.2", "sys": "^0.0.1", diff --git a/public/scripts/form.js b/public/scripts/form.js index 10608352..e96f454e 100644 --- a/public/scripts/form.js +++ b/public/scripts/form.js @@ -44,6 +44,8 @@ $(function () { $('#notification-container').css("visibility", "hidden"); $('#notification-container').css("opacity", "0"); }, 5000) + $('#btnDeploy').css("display", "inline"); + $('#btnDeploy').attr("href", '/github?email='+data.email+'&uniqueId='+data.uniqueId+'&gitURL='+data.gitUrl); }); socket.on('failure', function (data) { @@ -70,4 +72,4 @@ function getData() { }); return data; -} \ No newline at end of file +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index feb5ccaa..d0bee966 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -46,6 +46,12 @@ a { margin-left: 10px; } +#btnDeploy { + display: none; + width: -moz-fit-content; + margin-left: 10px; +} + #notification-container { position: absolute; text-align: center; @@ -81,4 +87,4 @@ a { .error { color: red; -} \ No newline at end of file +} diff --git a/publish_docs.sh b/publish_docs.sh index 306abb76..48b7a76e 100755 --- a/publish_docs.sh +++ b/publish_docs.sh @@ -5,16 +5,34 @@ # Environment Variables: # $DOCURL - Custom URL where the documentation would be published. +while getopts e:i:w:n:o:r: option +do + case "${option}" + in + e) EMAIL=${OPTARG};; + i) UNIQUEID=${OPTARG};; + w) WEBUI=${OPTARG};; + n) USERNAME=${OPTARG};; + o) OAUTH_TOKEN=${OPTARG};; + r) REPONAME=${OPTARG};; + esac +done + git config --global user.name "Yaydoc Bot" git config --global user.email "noreply+bot@example.com" GIT_SSH_URL=git@github.com:$USERNAME/$REPONAME.git - git clone --quiet $GIT_SSH_URL gh-pages if [ $? -ne 0 ]; then echo -e "Cloning using SSH failed. Trying with Github token instead\n" GIT_HTTPS_URL=https://$USERNAME:$OAUTH_TOKEN@github.com/$USERNAME/$REPONAME.git - git clone --quiet $GIT_HTTPS_URL gh-pages + if [ "${WEBUI:-false}" == "true" ]; then + cd temp/${EMAIL} + git clone --quiet $GIT_HTTPS_URL ${UNIQUEID}_pages + else + git clone --quiet $GIT_HTTPS_URL gh-pages + fi + if [[ $? -ne 0 ]]; then echo -e "Failed to clone gh-pages.\n" clean @@ -22,7 +40,14 @@ if [ $? -ne 0 ]; then fi fi -cd gh-pages +echo -e "Cloned successfully! \n" + +if [ "${WEBUI:-false}" == "true" ]; then + cd ${UNIQUEID}_pages +else + cd gh-pages +fi + # Create gh-pages branch if it doesn't exist git fetch @@ -32,12 +57,19 @@ fi # Overwrite files in the branch git rm -rfq ./* -cp -a ../_build/html/. ./ +if [ "${WEBUI:-false}" == "true" ]; then + cp -a ../${UNIQUEID}_preview/. ./ +else + cp -a ../_build/html/. ./ +fi + +echo -e "Overwrite successfully \n" # Enable publishing documentation to custom URL if [[ -z "${DOCURL}" ]]; then echo -e "DOCURL not set. Using default github pages URL" else + echo -e "DOCURL set." echo $DOCURL > CNAME fi @@ -46,5 +78,11 @@ git add -f . git commit -q -m "[Auto] Update Built Docs ($(date +%Y-%m-%d.%H:%M:%S))" git push origin gh-pages +echo -e "github pages pushed successfully!\n" # Cleanup -clean +if [ "${WEBUI:-false}" == "true" ]; then + cd .. + rm -r ${UNIQUEID}_pages +else + clean +fi diff --git a/routes/index.js b/routes/index.js index b1b7108f..707a33e7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,7 @@ var express = require("express"); var router = express.Router(); - +var passport = require("passport") +var crypter = require("../util/crypter.js") /* GET home page. */ router.get("/", function(req, res, next) { res.render("index", { title: "Yaydoc" }); @@ -12,4 +13,32 @@ router.get('/download/:email/:uniqueId', function (req, res, next) { res.download(file); }); +router.get("/github", function (req, res, next) { + req.session.uniqueId = req.query.uniqueId; + req.session.email = req.query.email + req.session.gitURL = req.query.gitURL + next() +}, passport.authenticate('github', { + scope: [ + 'public_repo', + 'read:org' + ] +})) + +router.get("/callback", passport.authenticate('github'), function (req, res, next) { + req.session.username = req.user.username; + req.session.token = req.user.token + res.redirect("/deploy") +}) + +router.get("/deploy", function (req, res, next) { + res.render("deploy", { + email: req.session.email, + gitURL: req.session.gitURL, + uniqueId: req.session.uniqueId, + token: crypter.encrypt(req.session.token), + username: req.session.username + }) +}) + module.exports = router; diff --git a/util/crypter.js b/util/crypter.js new file mode 100644 index 00000000..0288e097 --- /dev/null +++ b/util/crypter.js @@ -0,0 +1,30 @@ +const crypto = require('crypto'); + +const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // Must be 256 bytes (32 characters) +const IV_LENGTH = 16; // For AES, this is always 16 + +function encrypt(text) { + let iv = crypto.randomBytes(IV_LENGTH); + let cipher = crypto.createCipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv); + let encrypted = cipher.update(text); + + encrypted = Buffer.concat([encrypted, cipher.final()]); + + return iv.toString('hex') + ':' + encrypted.toString('hex'); +} + +function decrypt(text) { + let textParts = text.split(':'); + let iv = new Buffer(textParts.shift(), 'hex'); + let encryptedText = new Buffer(textParts.join(':'), 'hex'); + let decipher = crypto.createDecipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv); + let decrypted = decipher.update(encryptedText); + + decrypted = Buffer.concat([decrypted, decipher.final()]); + + return decrypted.toString(); +} + +exports.encrypt = encrypt + +exports.decrypt = decrypt diff --git a/views/deploy.jade b/views/deploy.jade new file mode 100644 index 00000000..a426db2f --- /dev/null +++ b/views/deploy.jade @@ -0,0 +1,32 @@ +extends layout + +block content + script(src="/socket.io/socket.io.js") + script. + var socket = io(); + socket.emit('deploy', { + 'email': "#{email}", + 'gitURL': "#{gitURL}", + 'uniqueId': "#{uniqueId}", + 'encryptedToken': "#{token}", + 'username': "#{username}" + }) + + socket.on('deploy-logs', function (data) { + $('#messages').append($('
  • ').text(data.data)); + $("#progress").css("width", data.donePercent + "%"); + }) + + socket.on('deploy-success', function(data) { + $("#progress").css("width", "100%"); + $("#main-content").append('
    GH-PAGES
    ') + }) + + .container + .col-md-6.col-md-offset-3#main-content + .progress + #progress.progress-bar(role='progressbar', aria-valuenow='0', aria-valuemin='0', aria-valuemax='100', style='width:0%') + span.sr-only + .logs.pre-scrollable + h4(style='text-align: center; padding-top: 5px; color: #ffffff') Console Output + ul#messages diff --git a/views/index.jade b/views/index.jade index 612c1962..405613a6 100644 --- a/views/index.jade +++ b/views/index.jade @@ -35,6 +35,7 @@ block content .row a.btn.btn-default(type='button' id='btnDownload') Download a.btn.btn-default(type='button' id='btnPreview' target='_blank') Preview + a.btn.btn-default(type='button' id='btnDeploy') Deploy .progress #progress.progress-bar(role='progressbar', aria-valuenow='0', aria-valuemin='0', aria-valuemax='100', style='width:0%') span.sr-only