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/zeit/now-static-build-starter/blob/master/Dockerfile
|
||||||
|
FROM mhart/alpine-node:10
|
||||||
# Source https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
|
# We store all our files in /usr/src to perform the build
|
||||||
# Installs latest Chromium package.
|
WORKDIR /usr/src
|
||||||
ENV CHROME_BIN=/usr/bin/chromium-browser
|
# We first add only the files required for installing deps
|
||||||
RUN apk update && apk upgrade && \
|
# If package.json or yarn.lock don't change, no need to re-install later
|
||||||
echo http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
|
COPY package.json yarn.lock ./
|
||||||
echo http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
|
# We install our deps
|
||||||
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
|
RUN yarn
|
||||||
|
# We copy all source files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
# We run the build and expose as /public
|
||||||
RUN yarn build
|
RUN yarn build && yarn export -o /public
|
||||||
|
|
||||||
# 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" ]
|
|
||||||
|
@ -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')
|
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",
|
"name": "carbon",
|
||||||
"type": "docker",
|
"type": "static",
|
||||||
"public": true,
|
"public": true,
|
||||||
"env": {
|
"static": {
|
||||||
"NODE_ENV": "production",
|
"rewrites": [
|
||||||
"TWITTER_CONSUMER_KEY": "@twitter-consumer-key",
|
{
|
||||||
"TWITTER_CONSUMER_SECRET": "@twitter-consumer-secret",
|
"source": "!/about",
|
||||||
"TWITTER_ACCESS_TOKEN_KEY": "@twitter-access-token-key",
|
"destination": "/index.html"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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