const express = require('express') const bodyParser = require('body-parser') const { vueRenderer } = require('@doweb/vuexpress') const slugify = require('underscore.string/slugify') const fs = require('fs') const { Seance } = require ('./seance') const config = require('./config') const app = express() var expressWs = require('express-ws')(app) // ALlow URLencoded API app.use(bodyParser.urlencoded({ extended: false })) // Allow JSON API app.use(bodyParser('json')) // Enable static files app.use('/', express.static('public')) // basePath is prefixed later app.use(config.basePath || '/', express.static('static')) // Router var router = express.Router() app.use(config.basePath || '/', router) // Set up VueXpress let options = { views: './views', cache: true, watch: true, metaInfo: { title: 'Seance', script: [ { type: 'text/javascript', src: (config.basePath || '') + '/app.js' }, ], }, extractCSS: true, cssOutputPath: (config.basePath || '') + '/css/styles.css', publicPath: 'public' + (config.basePath || ''), compilerConfig: { // custom webpack config }, compilerConfigCallback: function(webpackConfig) { // change the merged webpackconfig if you like return webpackConfig; }, } const renderer = vueRenderer(options) router.use(renderer) // Views router.get('/', (req, res) => { res.render('index', { seanceUrl: req.hostname.startsWith('localhost') ? req.headers.host + req.baseUrl : req.hostname + req.baseUrl, protocol: req.hostname.startsWith('localhost') ? req.protocol + '://' : '//', }) }) router.post('/fetch', (req, res) => { var json var post try { json = JSON.parse(req.body.data) } catch (err) { console.log(err) } if (json) { post = json.payload.value console.log(post) // set author post.author = post.displayAuthor // If the author's not available, get it from somewhere else // function courtesy mediumexporter let authors = [] if (json.payload.references && json.payload.references.User) { Object.keys(json.payload.references.User).forEach(k => { let u = json.payload.references.User[k] authors.push({ name: u.name, username: u.username, userId: u.userId }) }) post.authors = authors if (!post.author) { post.author = authors[0].name } } // set featured image if (post.virtuals.previewImage) { post.featuredImage = 'https://cdn-images-1.medium.com/max/800/' + post.virtuals.previewImage.imageId } // set ID if (!post.slug) { post.slug = slugify(post.title) } // save to disk fs.writeFileSync(`${post.slug}.json`, JSON.stringify(json), 'utf-8') } // render the final post res.render('fetch-medium', { seanceUrl: req.hostname.startsWith('localhost') ? req.headers.host + req.baseUrl : req.hostname + req.baseUrl, post: { title: post.title, subtitle: post.content.subtitle, author: post.author, featuredImage: post.featuredImage, mediumUrl: post.mediumUrl, slug: post.slug, } }) }) app.get('/fetch', (req, res) => { res.redirect(303, '/') }) router.get('/api', (req, res) => { res.json({ status: 'success', message: 'Welcome to the Seance API :)', }) }) router.ws('/ws/fetch-medium', (ws, req) => { ws.on('message', async(msg) => { command = msg.split(' ') if (command.length == 3 && command[0] == 'fetch') { const postSlug = command[1] const password = command[2] if (password != process.env.SEANCE_PW) { ws.send('error: Something went wrong. Please try again :(') return } const postMetaFile = `${postSlug}.json` if (!fs.existsSync(postMetaFile)) { ws.send(`error: post ${postSlug} not yet loaded`) return } // Start the Seance session seance = new Seance() // set up handlers seance.on('update', (e) => { ws.send(`update: ${e.message}`) }) seance.on('notification', (e) => { ws.send(`notification: ${e.message}`) }) seance.on('error', (e) => { ws.send(`error: ${e.message}`) }) seance.fetchFromMedium(postMetaFile) .then((post) => { console.info(`"${post.title}" fetched successfully.`) ws.send(`done: ${post.slug}`) }) } }) ws.on('close', () => { console.log('socket closed') }) }) router.ws('/ws/push-ghost', (ws, req) => { ws.on('message', async(msg) => { // respond to keepalive if (msg == '__ping__') { ws.send('__pong__') return } command = msg.split(' ') if (command.length == 3 && command[0] == 'push') { let postSlug = command[1] // check password if (command[2] != process.env.SEANCE_PW) { ws.send('error: Something went wrong. Please try again :(') return } // Start the Seance session seance = new Seance() // catch data let postId // set up handlers seance.on('update', (e) => { ws.send(`update: ${e.message}`) }) seance.on('notification', (e) => { ws.send(`notification: ${e.message}`) }) seance.on('error', (e) => { ws.send(`error: ${e.message}`) }) // run seance and wait till it finishes let res = await seance.pushToGhost(postSlug) console.info(`"${postSlug}" pushed successfully.`) // send 'done' message let ghostSite = await seance.ghostAdmin.site.read() let postEditUrl if (!!res.id) postEditUrl = `${ghostSite.url}ghost/#/editor/post/${res.id}` if (!!res.slug) postSlug = res.slug ws.send(`done: ${postSlug} ${postEditUrl}`) } else { ws.send('error: Malformed message') return } }) ws.on('close', () => { console.log('socket closed') }) }) const port = process.env.PORT || 4000 app.listen(port, () => { console.log(`Listening on ${port}`) })