Last active
January 20, 2018 13:54
-
-
Save igk1972/367b68b26495f47c1b571cdd38f720c5 to your computer and use it in GitHub Desktop.
Netlify CMS - api for file-system backend support JWT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.git | |
.gitignore | |
.dockerignore | |
docker-*.yml | |
node_modules | |
Makefile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM alpine:3.6 | |
RUN apk add --update-cache --no-cache nodejs yarn | |
ENV NODE_ENV=production | |
EXPOSE 8080 | |
ENTRYPOINT ["node", "main.js"] | |
CMD [] | |
RUN mkdir -p /app | |
WORKDIR /app/ | |
COPY . /app/ | |
RUN cd /app && yarn --prod --cache-folder /tmp/yarn && rm -fr /tmp/yarn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* Based on code https://github.com/netlify/netlify-cms/blob/api-file-system/scripts/fs/fs-api.js (Tony Alves) | |
* | |
**/ | |
const fs = require('fs'); | |
const path = require('path'); | |
let rootSite = process.env.SITE_ROOT || path.join(__dirname,'..','site'); | |
const siteRoot = { | |
setPath: (value) => { | |
rootSite = value; | |
}, | |
dir: rootSite | |
}; | |
console.log(`Site path is ${ siteRoot.dir }`); | |
module.exports = { | |
files: (dirname) => { | |
const name = "Files"; | |
const read = (cb) => { | |
if (!cb) throw new Error("Invalid call to files.read - requires a callback function(content)"); | |
const thispath = path.join(siteRoot.dir, dirname); | |
const files = fs.existsSync(thispath) ? fs.readdirSync(thispath) : []; | |
const filelist = []; | |
files.forEach(function(element) { | |
const filePath = path.join(thispath, element); | |
const stats = fs.statSync(filePath); | |
if (stats.isFile()) { | |
filelist.push({ name: element, path: `${ dirname }/${ element }`, stats, type: "file" }); | |
} | |
}, this); | |
cb(filelist); | |
}; | |
return { read, name }; | |
}, | |
file: (id) => { | |
const name = "File"; | |
const thisfile = path.join(siteRoot.dir, id); | |
let stats; | |
try { | |
stats = fs.statSync(thisfile); | |
} catch (err) { | |
stats = { }; | |
} | |
/* GET-Read an existing file */ | |
const read = (cb) => { | |
if (!cb) throw new Error("Invalid call to file.read - requires a callback function(content)"); | |
if (stats.isFile && stats.isFile()) { | |
fs.readFile(thisfile, 'utf8', (err, data) => { | |
if (err) { | |
cb({ error: err }); | |
} else { | |
cb(data); | |
} | |
}); | |
} else { | |
throw new Error("Invalid call to file.read - object path is not a file!"); | |
} | |
}; | |
/* POST-Create a NEW file, ERROR if exists */ | |
const create = (body, cb) => { | |
fs.writeFile(thisfile, body.content, { encoding: body.encoding, flag: 'wx' }, (err) => { | |
if (err) { | |
cb({ error: err }); | |
} else { | |
cb(body.content); | |
} | |
}); | |
}; | |
/* PUT-Update an existing file */ | |
const update = (body, cb) => { | |
fs.writeFile(thisfile, body.content, { encoding: body.encoding, flag: 'w' }, (err) => { | |
if (err) { | |
cb({ error: err }); | |
} else { | |
cb(body.content); | |
} | |
}); | |
}; | |
/* DELETE an existing file */ | |
const del = (cb) => { | |
fs.unlink(thisfile, (err) => { | |
if (err) { | |
cb({ error: err }); | |
} else { | |
cb(`Deleted File ${ thisfile }`); | |
} | |
}); | |
}; | |
return { read, create, update, del, stats }; | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* Based on code https://github.com/netlify/netlify-cms/blob/api-file-system/scripts/fs/fs-express-api.js (Tony Alves) | |
* | |
**/ | |
const bodyParser = require('body-parser'); | |
const multer = require('multer'); | |
const fsAPI = require('./fs-api'); | |
const JWT = require('jwt-decode'); | |
/* Express allows for app object setup to handle paths (our api routes) */ | |
module.exports = function(app) { | |
const upload = multer(); // for parsing multipart/form-data | |
const uploadLimit = '50mb'; // express has a default of ~20Kb | |
app.use(bodyParser.json({ limit: uploadLimit })); // for parsing application/json | |
app.use(bodyParser.urlencoded({ limit: uploadLimit, extended: true, parameterLimit:50000 })); // for parsing application/x-www-form-urlencoded | |
// We will look at every route to bypass any /api route from the react app | |
app.use('/:path', function(req, res, next) { | |
const response = { }; | |
// if the path is api, skip to the next route | |
if (req.params.path === 'api') { | |
if (process.env.NODE_ENV == 'production') { | |
if (req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer')){ | |
const jwt_token = req.headers['authorization'].substr(7); | |
const jwt_data = JWT(jwt_token); | |
next('route'); | |
} else { | |
response.status = 403; | |
response.error = 'Need authorization'; | |
res.status(response.status).json(response); | |
} | |
} else { | |
next('route'); | |
} | |
} | |
// otherwise pass the control out of this middleware to the next middleware function in this stack (back to regular) | |
else next(); | |
}); | |
app.use('/api', function(req, res, next) { | |
const response = { route: '/api', url: req.originalUrl }; | |
if (req.originalUrl === "/api" || req.originalUrl === "/api/") { | |
// if the requested url is the root, , respond Error! | |
response.status = 500; | |
response.error = 'This is the root of the API'; | |
res.status(response.status).json(response); | |
} else { | |
// continue to the next sub-route ('/api/:path') | |
next('route'); | |
} | |
}); | |
/* Define custom handlers for api paths: */ | |
app.use('/api/:path', function(req, res, next) { | |
const response = { route: '/api/:path', path: req.params.path, params: req.params }; | |
if (req.params.path && req.params.path in fsAPI) { | |
// all good, route exists in the api | |
next('route'); | |
} else { | |
// sub-route was not found in the api, respond Error! | |
response.status = 500; | |
response.error = `Invalid path ${ req.params.path }`; | |
res.status(response.status).json(response); | |
} | |
}); | |
/* Files */ | |
/* Return all the files in the starting path */ | |
app.get('/api/files', function(req, res, next) { | |
const response = { route: '/api/files' }; | |
try { | |
fsAPI.files('./').read((contents) => { | |
res.json(contents); | |
}); | |
} catch (err) { | |
response.status = 500; | |
response.error = `Could not get files - code [${ err.code }]`; | |
response.internalError = err; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Return all the files in the passed path */ | |
app.get('/api/files/:path', function(req, res, next) { | |
const response = { route: '/api/files/:path', params: req.params, path: req.params.path }; | |
try { | |
fsAPI.files(req.params.path).read((contents) => { | |
res.json(contents); | |
}); | |
} catch (err) { | |
response.status = 500; | |
response.error = `Could not get files for ${ req.params.path } - code [${ err.code }]`; | |
response.internalError = err; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Capture Unknown extras and handle path (ignore?) */ | |
app.get('/api/files/:path/**', function(req, res, next) { | |
const response = { route: '/api/files/:path/**', params: req.params, path: req.params.path }; | |
const filesPath = req.originalUrl.substring(11, req.originalUrl.split('?', 1)[0].length); | |
try { | |
fsAPI.files(filesPath).read((contents) => { | |
res.json(contents); | |
}); | |
} catch (err) { | |
response.status = 500; | |
response.error = `Could not get files for ${ filesPath } - code [${ err.code }]`; | |
response.internalError = err; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* File */ | |
app.get('/api/file', function(req, res, next) { | |
const response = { error: 'Id cannot be empty for file', status: 500, path: res.path }; | |
res.status(response.status).send(response); | |
}); | |
app.get('/api/file/:id', function(req, res, next) { | |
const response = { route: '/api/file/:id', id: req.params.id }; | |
const allDone = (contents) => { | |
if (contents.error) { | |
response.status = 500; | |
response.error = `Could not read file ${ req.params.id } - code [${ contents.error.code }]`; | |
response.internalError = contents.error; | |
res.status(response.status).send(response); | |
} else { | |
res.json(contents); | |
} | |
}; | |
if (req.params.id) { | |
fsAPI.file(req.params.id).read(allDone); | |
} else { | |
response.status = 500; | |
response.error = `Invalid id for File ${ req.params.id }`; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Capture Unknown extras and ignore the rest */ | |
app.get('/api/file/:id/**', function(req, res, next) { | |
const response = { route: '/api/file/:id', id: req.params.id, method:req.method }; | |
const filePath = req.originalUrl.substring(10, req.originalUrl.split('?', 1)[0].length); | |
const allDone = (contents) => { | |
if (contents.error) { | |
response.status = 500; | |
response.error = `Could not read file ${ filePath } - code [${ contents.error.code }]`; | |
response.internalError = contents.error; | |
res.status(response.status).send(response); | |
} else { | |
res.json(contents); | |
} | |
}; | |
if (filePath) { | |
fsAPI.file(filePath).read(allDone); | |
} else { | |
response.status = 500; | |
response.error = `Invalid path for File ${ filePath }`; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Create file if path does not exist */ | |
app.post('/api/file/:id/**', upload.array(), function(req, res, next) { | |
const response = { route: '/api/file/:id', id: req.params.id, method:req.method }; | |
const filePath = req.originalUrl.substring(10, req.originalUrl.split('?', 1)[0].length); | |
const allDone = (contents) => { | |
if (contents.error) { | |
response.status = 500; | |
response.error = `Could not create file ${ filePath } - code [${ contents.error.code }]`; | |
response.internalError = contents.error; | |
res.status(response.status).send(response); | |
} else { | |
res.json(contents); | |
} | |
}; | |
if (filePath) { | |
fsAPI.file(filePath).create(req.body, allDone); | |
} else { | |
response.status = 500; | |
response.error = `Invalid path for File ${ filePath }`; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Update file, error on path exists */ | |
app.put('/api/file/:id/**', upload.array(), function(req, res, next) { | |
const response = { route: '/api/file/:id', id: req.params.id, method:req.method }; | |
const filePath = req.originalUrl.substring(10, req.originalUrl.split('?', 1)[0].length); | |
const allDone = (contents) => { | |
if (contents.error) { | |
response.status = 500; | |
response.error = `Could not update file ${ filePath } - code [${ contents.error.code }]`; | |
response.internalError = contents.error; | |
res.status(response.status).send(response); | |
} else { | |
res.json(contents); | |
} | |
}; | |
if (filePath) { | |
fsAPI.file(filePath).update(req.body, allDone); | |
} else { | |
response.status = 500; | |
response.error = `Invalid path for File ${ filePath }`; | |
res.status(response.status).send(response); | |
} | |
}); | |
/* Delete file, error if no file */ | |
app.delete('/api/file/:id/**', function(req, res, next) { | |
const response = { route: '/api/file/:id', id: req.params.id, method:req.method }; | |
const filePath = req.originalUrl.substring(10, req.originalUrl.split('?', 1)[0].length); | |
const allDone = (contents) => { | |
if (contents.error) { | |
response.status = 500; | |
response.error = `Could not delete file ${ filePath } - code [${ contents.error.code }]`; | |
response.internalError = contents.error; | |
res.status(response.status).send(response); | |
} else { | |
res.json(contents); | |
} | |
}; | |
if (filePath) { | |
fsAPI.file(filePath).del(allDone); | |
} else { | |
response.status = 500; | |
response.error = `Invalid path for File ${ filePath }`; | |
res.status(response.status).send(response); | |
} | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express') | |
module.exports = function(app) { | |
app.use('/', express.static('site/static/admin')) | |
app.use('/media', express.static('site/static/media')) | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const app = require('express')() | |
app.set('port', process.env.PORT || 8080) | |
require('./fs-express-static')(app) | |
require('./fs-express-api')(app) | |
app.disable('x-powered-by') | |
const server = app.listen(app.get('port'), () => console.log(`Server is running on port ${server.address().port}`)) | |
process.on('SIGINT', function() { | |
if (process.env.NODE_ENV == 'development') { | |
console.log('Closed server'); | |
process.exit(0); | |
} | |
console.log('Graceful shutdown server'); | |
server.close(function () { | |
console.log('Closed server'); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
build: | |
docker -t netlify-cms-api_nodejs . | |
start: | |
docker run -d --name netlify-cms_admin -p 8080:8080 -v /path/to/project/site:/app/site -e NODE_ENV=development netlify-cms-api_nodejs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "admin-netlify-cms", | |
"private": true, | |
"scripts": { | |
"start": "node main.js ${@}" | |
}, | |
"devDependencies": { | |
"babel-core": "^6.0.15", | |
"babel-preset-env": "^1.6.0" | |
}, | |
"dependencies": { | |
"express": "^4.16.2", | |
"jwt-decode": "^2.2.0", | |
"multer": "^1.3.0" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment