mirror of https://github.com/sgoudham/carbon.git
Separate API service, deploy frontend statically (#474)
* extract server into separate service * fix basic tests with url.parse * use Next withRouter * remove old custom next renderingmain
parent
f80c835328
commit
0580e1c8e8
@ -1,38 +1,13 @@
|
||||
FROM node:9-alpine
|
||||
|
||||
# Source https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
|
||||
# Installs latest Chromium package.
|
||||
ENV CHROME_BIN=/usr/bin/chromium-browser
|
||||
RUN apk update && apk upgrade && \
|
||||
echo http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
|
||||
echo http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
|
||||
apk add --no-cache \
|
||||
chromium \
|
||||
nss
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
COPY yarn.lock ./
|
||||
|
||||
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
||||
|
||||
# Source: https://github.com/zeit/now-static-build-starter/blob/master/Dockerfile
|
||||
FROM mhart/alpine-node:10
|
||||
# We store all our files in /usr/src to perform the build
|
||||
WORKDIR /usr/src
|
||||
# We first add only the files required for installing deps
|
||||
# If package.json or yarn.lock don't change, no need to re-install later
|
||||
COPY package.json yarn.lock ./
|
||||
# We install our deps
|
||||
RUN yarn
|
||||
|
||||
# We copy all source files
|
||||
COPY . .
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# Add user so we don't need --no-sandbox.
|
||||
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
|
||||
&& mkdir -p /home/pptruser/Downloads \
|
||||
&& chown -R pptruser:pptruser /home/pptruser \
|
||||
&& chown -R pptruser:pptruser /app
|
||||
|
||||
# Run everything after as non-privileged user.
|
||||
USER pptruser
|
||||
|
||||
ENV NODE_ENV production
|
||||
EXPOSE 3000
|
||||
CMD [ "yarn", "start" ]
|
||||
# We run the build and expose as /public
|
||||
RUN yarn build && yarn export -o /public
|
||||
|
@ -0,0 +1,36 @@
|
||||
FROM node:9-alpine
|
||||
|
||||
# Source https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
|
||||
# Installs latest Chromium package.
|
||||
ENV CHROME_BIN=/usr/bin/chromium-browser
|
||||
RUN apk update && apk upgrade && \
|
||||
echo http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
|
||||
echo http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
|
||||
apk add --no-cache \
|
||||
chromium \
|
||||
nss
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
COPY yarn.lock ./
|
||||
|
||||
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
||||
|
||||
RUN yarn
|
||||
|
||||
COPY . .
|
||||
|
||||
# Add user so we don't need --no-sandbox.
|
||||
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
|
||||
&& mkdir -p /home/pptruser/Downloads \
|
||||
&& chown -R pptruser:pptruser /home/pptruser \
|
||||
&& chown -R pptruser:pptruser /app
|
||||
|
||||
# Run everything after as non-privileged user.
|
||||
USER pptruser
|
||||
|
||||
ENV NODE_ENV production
|
||||
EXPOSE 4000
|
||||
CMD [ "node", "server.js" ]
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "carbon-api",
|
||||
"alias": "carbon-api.now.sh",
|
||||
"type": "docker",
|
||||
"public": true,
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"TWITTER_CONSUMER_KEY": "@twitter-consumer-key",
|
||||
"TWITTER_CONSUMER_SECRET": "@twitter-consumer-secret",
|
||||
"TWITTER_ACCESS_TOKEN_KEY": "@twitter-access-token-key",
|
||||
"TWITTER_ACCESS_TOKEN_SECRET": "@twitter-access-token-secret",
|
||||
"LOGS_SECRET_PREFIX": "@logs_secret_prefix",
|
||||
"UNSPLASH_SECRET_KEY": "@unsplash_secret_key",
|
||||
"UNSPLASH_ACCESS_KEY": "@unsplash_access_key",
|
||||
"UNSPLASH_CALLBACK_URL": "@unsplash_callback_url"
|
||||
},
|
||||
"features": {
|
||||
"cloud": "v1"
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "carbon-api",
|
||||
"version": "0.0.0-semantically-released",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"start": "node server.js",
|
||||
"deploy": "now"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.17.2",
|
||||
"compression": "^1.7.3",
|
||||
"cors": "^2.8.4",
|
||||
"express": "^4.16.2",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"morgan": "^1.8.2",
|
||||
"morphmorph": "^0.1.2",
|
||||
"now-logs": "^0.0.7",
|
||||
"puppeteer": "1.7.0",
|
||||
"twit": "^2.2.9",
|
||||
"unsplash-js": "^4.8.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
const express = require('express')
|
||||
const cors = require('cors')
|
||||
const compression = require('compression')
|
||||
const morgan = require('morgan')
|
||||
const bodyParser = require('body-parser')
|
||||
const puppeteer = require('puppeteer')
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 4000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
|
||||
process.on('SIGINT', process.exit)
|
||||
|
||||
if (!dev) {
|
||||
const LOGS_ID = `${process.env.LOGS_SECRET_PREFIX}:${process.env.NOW_URL}`
|
||||
require('now-logs')(LOGS_ID)
|
||||
}
|
||||
|
||||
function wrap(handler) {
|
||||
return (req, res) =>
|
||||
handler(req, res).catch(err => {
|
||||
// eslint-disable-next-line
|
||||
console.log('ERR:', err)
|
||||
res.status(400).end()
|
||||
})
|
||||
}
|
||||
|
||||
const puppeteerParams = dev
|
||||
? {}
|
||||
: {
|
||||
executablePath: '/usr/bin/chromium-browser',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
}
|
||||
|
||||
puppeteer.launch(puppeteerParams).then(browser => {
|
||||
// set up
|
||||
const server = express()
|
||||
const imageHandler = require('./handlers/image')(browser)
|
||||
const unsplashHandler = require('./handlers/unsplash')
|
||||
|
||||
if (dev) {
|
||||
server.use(morgan('tiny'))
|
||||
}
|
||||
|
||||
server.use(cors())
|
||||
|
||||
server.use(compression())
|
||||
|
||||
// Service Worker
|
||||
// const filePath = path.join(__dirname, '.next', 'service-worker.js')
|
||||
// server.get('/service-worker.js', (req, res) => app.serveStatic(req, res, filePath))
|
||||
|
||||
// api endpoints
|
||||
server.post('/twitter', bodyParser.json({ limit: '5mb' }), require('./handlers/twitter'))
|
||||
server.post('/image', bodyParser.json({ limit: '5mb' }), wrap(imageHandler))
|
||||
server.get('/unsplash/random', wrap(unsplashHandler.randomImages))
|
||||
server.get('/unsplash/download/:imageId', wrap(unsplashHandler.downloadImage))
|
||||
|
||||
server.listen(port, '0.0.0.0', err => {
|
||||
if (err) throw err
|
||||
// eslint-disable-next-line
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,26 @@
|
||||
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants')
|
||||
const withOffline = require('next-offline')
|
||||
|
||||
module.exports = withOffline()
|
||||
module.exports = (phase /* { defaultConfig } */) => {
|
||||
const config = {
|
||||
exportPathMap() {
|
||||
return {
|
||||
'/about': { page: '/about' },
|
||||
'/index': { page: '/index' },
|
||||
'/': { page: '/' }
|
||||
}
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
API_URL:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://carbon-api.now.sh'
|
||||
: 'http://localhost:4000'
|
||||
}
|
||||
}
|
||||
|
||||
if (phase === PHASE_DEVELOPMENT_SERVER) {
|
||||
return config
|
||||
}
|
||||
|
||||
return withOffline(config)
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
{
|
||||
"name": "carbon",
|
||||
"type": "docker",
|
||||
"type": "static",
|
||||
"public": true,
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"TWITTER_CONSUMER_KEY": "@twitter-consumer-key",
|
||||
"TWITTER_CONSUMER_SECRET": "@twitter-consumer-secret",
|
||||
"TWITTER_ACCESS_TOKEN_KEY": "@twitter-access-token-key",
|
||||
"TWITTER_ACCESS_TOKEN_SECRET": "@twitter-access-token-secret",
|
||||
"LOGS_SECRET_PREFIX": "@logs_secret_prefix",
|
||||
"UNSPLASH_SECRET_KEY": "@unsplash_secret_key",
|
||||
"UNSPLASH_ACCESS_KEY": "@unsplash_access_key",
|
||||
"UNSPLASH_CALLBACK_URL": "@unsplash_callback_url"
|
||||
},
|
||||
"features": {
|
||||
"cloud": "v1"
|
||||
"static": {
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "!/about",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
const path = require('path')
|
||||
const express = require('express')
|
||||
const compression = require('compression')
|
||||
const morgan = require('morgan')
|
||||
const bodyParser = require('body-parser')
|
||||
const next = require('next')
|
||||
const puppeteer = require('puppeteer')
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 3000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
|
||||
process.on('SIGINT', process.exit)
|
||||
|
||||
if (!dev) {
|
||||
const LOGS_ID = `${process.env.LOGS_SECRET_PREFIX}:${process.env.NOW_URL}`
|
||||
require('now-logs')(LOGS_ID)
|
||||
}
|
||||
|
||||
function wrap(handler) {
|
||||
return (req, res) =>
|
||||
handler(req, res).catch(err => {
|
||||
// eslint-disable-next-line
|
||||
console.log('ERR:', err)
|
||||
res.status(400).end()
|
||||
})
|
||||
}
|
||||
|
||||
const puppeteerParams = dev
|
||||
? {}
|
||||
: {
|
||||
executablePath: '/usr/bin/chromium-browser',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
}
|
||||
|
||||
app
|
||||
.prepare()
|
||||
.then(puppeteer.launch.bind(puppeteer, puppeteerParams))
|
||||
.then(browser => {
|
||||
// set up
|
||||
const server = express()
|
||||
const imageHandler = require('./handlers/image')(browser)
|
||||
const unsplashHandler = require('./handlers/unsplash')
|
||||
|
||||
if (dev) {
|
||||
server.use(morgan('tiny'))
|
||||
}
|
||||
|
||||
server.use(compression())
|
||||
|
||||
const filePath = path.join(__dirname, '.next', 'service-worker.js')
|
||||
server.get('/service-worker.js', (req, res) => app.serveStatic(req, res, filePath))
|
||||
// api endpoints
|
||||
server.post('/twitter', bodyParser.json({ limit: '5mb' }), require('./handlers/twitter'))
|
||||
server.post('/image', bodyParser.json({ limit: '5mb' }), wrap(imageHandler))
|
||||
server.get('/unsplash/random', wrap(unsplashHandler.randomImages))
|
||||
server.get('/unsplash/download/:imageId', wrap(unsplashHandler.downloadImage))
|
||||
|
||||
server.get('/about', (req, res) => app.render(req, res, '/about'))
|
||||
|
||||
// if root, render webpage from next
|
||||
server.get('/*', (req, res) => app.render(req, res, '/', req.query))
|
||||
|
||||
// otherwise, try and get gist
|
||||
server.get('*', handle)
|
||||
|
||||
server.listen(port, '0.0.0.0', err => {
|
||||
if (err) throw err
|
||||
// eslint-disable-next-line
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue