Rewrite frontend using create-react-app
This commit is contained in:
93
server/cloudflare.js
Normal file
93
server/cloudflare.js
Normal file
@ -0,0 +1,93 @@
|
||||
require('isomorphic-fetch')
|
||||
const { createStack, createFetch, header, base, query, parseJSON, onResponse } = require('http-client')
|
||||
const invariant = require('invariant')
|
||||
|
||||
const CloudflareKey = process.env.CLOUDFLARE_KEY
|
||||
const CloudflareEmail = process.env.CLOUDFLARE_EMAIL
|
||||
|
||||
invariant(
|
||||
CloudflareKey,
|
||||
'Missing $CLOUDFLARE_KEY environment variable'
|
||||
)
|
||||
|
||||
invariant(
|
||||
CloudflareEmail,
|
||||
'Missing $CLOUDFLARE_EMAIL environment variable'
|
||||
)
|
||||
|
||||
const createRangeQuery = (since, until) =>
|
||||
query({
|
||||
since: since.toISOString(),
|
||||
until: until.toISOString()
|
||||
})
|
||||
|
||||
const createNameQuery = (name) =>
|
||||
query({ name })
|
||||
|
||||
const getResult = () =>
|
||||
createStack(
|
||||
parseJSON(),
|
||||
onResponse(response => response.jsonData.result)
|
||||
)
|
||||
|
||||
const commonStack = createStack(
|
||||
header('X-Auth-Key', CloudflareKey),
|
||||
header('X-Auth-Email', CloudflareEmail),
|
||||
base('https://api.cloudflare.com/client/v4'),
|
||||
getResult()
|
||||
)
|
||||
|
||||
const getZones = (domainName) =>
|
||||
createFetch(
|
||||
commonStack,
|
||||
createNameQuery(domainName)
|
||||
)('/zones')
|
||||
|
||||
const getZoneAnalyticsDashboard = (zone, since, until) =>
|
||||
createFetch(
|
||||
commonStack,
|
||||
createRangeQuery(since, until)
|
||||
)(`/zones/${zone.id}/analytics/dashboard`)
|
||||
|
||||
const getAnalyticsDashboards = (domainNames, since, until) =>
|
||||
Promise.all(
|
||||
domainNames.map(domainName => getZones(domainName))
|
||||
).then(
|
||||
domainZones => domainZones.reduce((memo, zones) => memo.concat(zones))
|
||||
).then(
|
||||
zones => Promise.all(zones.map(zone => getZoneAnalyticsDashboard(zone, since, until)))
|
||||
).then(
|
||||
results => results.reduce(reduceResults)
|
||||
)
|
||||
|
||||
const reduceResults = (target, results) => {
|
||||
Object.keys(results).forEach(key => {
|
||||
const value = results[key]
|
||||
|
||||
if (typeof value === 'object' && value) {
|
||||
target[key] = reduceResults(target[key] || {}, value)
|
||||
} else if (typeof value === 'number') {
|
||||
target[key] = (target[key] || 0) + results[key]
|
||||
}
|
||||
})
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
const OneMinute = 1000 * 60
|
||||
const ThirtyDays = OneMinute * 60 * 24 * 30
|
||||
|
||||
const fetchStats = (callback) => {
|
||||
const since = new Date(Date.now() - ThirtyDays)
|
||||
const until = new Date(Date.now() - OneMinute)
|
||||
|
||||
getAnalyticsDashboards([ 'npmcdn.com', 'unpkg.com' ], since, until)
|
||||
.then(result => callback(null, result), callback)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getZones,
|
||||
getZoneAnalyticsDashboard,
|
||||
getAnalyticsDashboards,
|
||||
fetchStats
|
||||
}
|
17445
server/dev-data.json
Normal file
17445
server/dev-data.json
Normal file
File diff suppressed because it is too large
Load Diff
111
server/index.js
Normal file
111
server/index.js
Normal file
@ -0,0 +1,111 @@
|
||||
/*eslint-disable no-console*/
|
||||
const http = require('http')
|
||||
const express = require('express')
|
||||
const cors = require('cors')
|
||||
const morgan = require('morgan')
|
||||
const unpkg = require('express-unpkg')
|
||||
const { fetchStats } = require('./cloudflare')
|
||||
const { logPackageRequests } = require('./logging')
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const sendHomePage = (publicDir) => {
|
||||
const html = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf8')
|
||||
|
||||
return (req, res, next) => {
|
||||
fetchStats((error, stats) => {
|
||||
if (error) {
|
||||
next(error)
|
||||
} else {
|
||||
res.set('Cache-Control', 'public, max-age=60')
|
||||
res.send(
|
||||
html.replace('__SERVER_DATA__', JSON.stringify({
|
||||
cloudflareStats: stats
|
||||
}))
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
res.status(500).send('<p>Internal Server Error</p>')
|
||||
console.error(err.stack)
|
||||
next(err)
|
||||
}
|
||||
|
||||
const createServer = (config) => {
|
||||
const app = express()
|
||||
|
||||
app.disable('x-powered-by')
|
||||
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
app.use(morgan('dev'))
|
||||
|
||||
app.use(errorHandler)
|
||||
app.use(cors())
|
||||
|
||||
app.get('/', sendHomePage(config.publicDir))
|
||||
|
||||
app.use(express.static(config.publicDir, {
|
||||
maxAge: config.maxAge
|
||||
}))
|
||||
|
||||
if (config.redisURL)
|
||||
app.use(logPackageRequests(config.redisURL))
|
||||
|
||||
app.use(unpkg.createRequestHandler(config))
|
||||
|
||||
const server = http.createServer(app)
|
||||
|
||||
// Heroku dynos automatically timeout after 30s. Set our
|
||||
// own timeout here to force sockets to close before that.
|
||||
// https://devcenter.heroku.com/articles/request-timeout
|
||||
if (config.timeout) {
|
||||
server.setTimeout(config.timeout, (socket) => {
|
||||
const message = `Timeout of ${config.timeout}ms exceeded`
|
||||
|
||||
socket.end([
|
||||
`HTTP/1.1 503 Service Unavailable`,
|
||||
`Date: ${(new Date).toGMTString()}`,
|
||||
`Content-Type: text/plain`,
|
||||
`Content-Length: ${message.length}`,
|
||||
`Connection: close`,
|
||||
``,
|
||||
message
|
||||
].join(`\r\n`))
|
||||
})
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
const defaultServerConfig = {
|
||||
id: 1,
|
||||
port: parseInt(process.env.PORT, 10) || 5000,
|
||||
publicDir: 'public',
|
||||
timeout: parseInt(process.env.TIMEOUT, 10) || 20000,
|
||||
maxAge: process.env.MAX_AGE || '365d',
|
||||
|
||||
// for express-unpkg
|
||||
registryURL: process.env.REGISTRY_URL || 'https://registry.npmjs.org',
|
||||
redirectTTL: process.env.REDIRECT_TTL || 500,
|
||||
autoIndex: !process.env.DISABLE_INDEX,
|
||||
redisURL: process.env.REDIS_URL,
|
||||
blacklist: require('./package-blacklist').blacklist
|
||||
}
|
||||
|
||||
const startServer = (serverConfig = {}) => {
|
||||
const config = Object.assign({}, defaultServerConfig, serverConfig)
|
||||
const server = createServer(config)
|
||||
|
||||
server.listen(config.port, () => {
|
||||
console.log('Server #%s listening on port %s, Ctrl+C to stop', config.id, config.port)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createServer,
|
||||
startServer
|
||||
}
|
31
server/logging.js
Normal file
31
server/logging.js
Normal file
@ -0,0 +1,31 @@
|
||||
const redis = require('redis')
|
||||
const onFinished = require('on-finished')
|
||||
|
||||
const URLFormat = /^\/((?:@[^\/@]+\/)?[^\/@]+)(?:@([^\/]+))?(\/.*)?$/
|
||||
|
||||
const logPackageRequests = (redisURL) => {
|
||||
const redisClient = redis.createClient(redisURL)
|
||||
|
||||
return (req, res, next) => {
|
||||
onFinished(res, () => {
|
||||
const path = req.path
|
||||
|
||||
if (res.statusCode === 200 && path.charAt(path.length - 1) !== '/') {
|
||||
//redisClient.zincrby([ 'request-paths', 1, path ])
|
||||
|
||||
const match = URLFormat.exec(path)
|
||||
|
||||
if (match) {
|
||||
const packageName = match[1]
|
||||
redisClient.zincrby([ 'package-requests', 1, packageName ])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logPackageRequests
|
||||
}
|
6
server/package-blacklist.json
Normal file
6
server/package-blacklist.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"blacklist": [
|
||||
"goodjsproject",
|
||||
"thisoneisevil"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user