diff --git a/.gitignore b/.gitignore index 13dfa3637..1b2c41a16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env -node_modules/ \ No newline at end of file +node_modules/ +.vscode/ \ No newline at end of file diff --git a/classes/ImageFile.js b/classes/ImageFile.js new file mode 100644 index 000000000..99034152c --- /dev/null +++ b/classes/ImageFile.js @@ -0,0 +1,172 @@ +const axios = require('axios'); +const _ = require('lodash') + +const GITHUB_ORG_NAME = 'isomerpages' + +// validateStatus allows axios to handle a 404 HTTP status without rejecting the promise. +// This is necessary because GitHub returns a 404 status when the file does not exist. +const validateStatus = (status) => { + return (status >= 200 && status < 300) || status === 404 +} + +class ImageFile { + constructor(accessToken, siteName) { + this.accessToken = accessToken + this.siteName = siteName + this.baseEndpoint = null + this.blobEndpoint = null + this.fileType = null + } + + setFileTypeToImage() { + this.fileType = new ImageType() + this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/${this.fileType.getFolderName()}` + // Endpoint to retrieve files greater than 1MB + this.baseBlobEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/git/blobs` + } + + + + async list() { + try { + const endpoint = `${this.baseEndpoint}` + + const resp = await axios.get(endpoint, { + validateStatus: validateStatus, + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json" + } + }) + + if (resp.status !== 200) return {} + + const files = resp.data.map(object => { + const pathNameSplit = object.path.split("/") + const fileName = pathNameSplit[pathNameSplit.length - 1] + if (object.type === 'file') { + return { + path: encodeURIComponent(object.path), + fileName, + sha: object.sha + } + } + }) + + return _.compact(files) + } catch (err) { + throw err + } + } + + async create(fileName, content) { + try { + const endpoint = `${this.baseEndpoint}/${fileName}` + + let params = { + "message": `Create file: ${fileName}`, + "content": content, + "branch": "staging", + } + + const resp = await axios.put(endpoint, params, { + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json" + } + }) + + return { sha: resp.data.content.sha } + } catch (err) { + throw err + } + } + + async read(fileName) { + try { + /** + * Images that are bigger than 1 MB needs to be retrieved + * via Github Blob API. The content can only be retrieved through + * the `sha` of the file. + * The code below takes the `fileName`, + * lists all the files in the image directory + * and filters it down to get the sha of the file + */ + const images = await this.list() + const imageSha = images.filter(image => image.fileName === fileName)[0].sha + + const blobEndpoint = `${this.baseBlobEndpoint}/${imageSha}` + + const resp = await axios.get(blobEndpoint, { + validateStatus: validateStatus, + headers: { + Authorization: `token ${this.accessToken}`, + } + }) + + if (resp.status === 404) throw new Error ('Page does not exist') + + const { content, sha } = resp.data + + return { content, sha } + } catch (err) { + throw err + } + } + + async update(fileName, content, sha) { + try { + const endpoint = `${this.baseEndpoint}/${fileName}` + + let params = { + "message": `Update file: ${fileName}`, + "content": content, + "branch": "staging", + "sha": sha + } + + const resp = await axios.put(endpoint, params, { + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json" + } + }) + + return { newSha: resp.data.commit.sha } + } catch (err) { + throw err + } + } + + async delete (fileName, sha) { + try { + const endpoint = `${this.baseEndpoint}/${fileName}` + + let params = { + "message": `Delete file: ${fileName}`, + "branch": "staging", + "sha": sha + } + + await axios.delete(endpoint, { + data: params, + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json" + } + }) + } catch (err) { + throw err + } + } +} + +class ImageType { + constructor() { + this.folderName = 'images' + } + getFolderName() { + return this.folderName + } +} +module.exports = { ImageFile } diff --git a/routes/images.js b/routes/images.js index 33e4ec5cd..f67e80a5a 100644 --- a/routes/images.js +++ b/routes/images.js @@ -4,6 +4,7 @@ const jwtUtils = require('../utils/jwt-utils') // Import classes const { File, ImageType } = require('../classes/File.js') +const { ImageFile } = require('../classes/ImageFile.js') // List images router.get('/:siteName/images', async function(req, res, next) { @@ -54,10 +55,9 @@ router.get('/:siteName/images/:imageName', async function(req, res, next) { const { siteName, imageName } = req.params - const IsomerFile = new File(access_token, siteName) - const imageType = new ImageType() - IsomerFile.setFileType(imageType) - const { sha, content } = await IsomerFile.read(imageName) + const IsomerImageFile = new ImageFile(access_token, siteName) + IsomerImageFile.setFileTypeToImage() + const { sha, content } = await IsomerImageFile.read(imageName) // TO-DO: // Validate content diff --git a/server.js b/server.js index b55ac69a7..3ab508f84 100644 --- a/server.js +++ b/server.js @@ -26,7 +26,7 @@ const homepageRouter = require('./routes/homepage') const app = express(); app.use(logger('dev')); -app.use(express.json()); +app.use(express.json({ limit: '5mb'})); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); @@ -64,7 +64,7 @@ app.use(function(err, req, res, next) { // render the error page res.status(err.status || 500); - res.render('error'); + res.json({ error: err }); }); module.exports = app;