Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge to prod 12 May #174

Merged
merged 5 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions classes/ImageFile.js → classes/MediaFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ const _ = require('lodash')
const validateStatus = require('../utils/axios-utils')

// Import error
const { NotFoundError } = require('../errors/NotFoundError')
const { NotFoundError } = require('../errors/NotFoundError')
const { ConflictError, inputNameConflictErrorMsg } = require('../errors/ConflictError')

// Constants
const GITHUB_ORG_NAME = 'isomerpages'

class ImageFile {
class MediaFile {
constructor(accessToken, siteName) {
this.accessToken = accessToken
this.siteName = siteName
Expand All @@ -17,14 +18,19 @@ class ImageFile {
this.fileType = null
}

setFileTypeToImage() {
this.fileType = new ImageType()
setFileTypeToImage(directory) {
this.fileType = new ImageType(directory)
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`
}


setFileTypeToDocument(directory) {
this.fileType = new DocumentType(directory)
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 {
Expand Down Expand Up @@ -77,7 +83,9 @@ class ImageFile {

return { sha: resp.data.content.sha }
} catch (err) {
throw err
const status = err.response.status
if (status === 422 || status === 409) throw new ConflictError(inputNameConflictErrorMsg(fileName))
throw err.response
}
}

Expand Down Expand Up @@ -161,11 +169,20 @@ class ImageFile {
}

class ImageType {
constructor() {
this.folderName = 'images'
constructor(directory) {
this.folderName = directory ? directory : 'images'
}
getFolderName() {
return this.folderName
}
}

class DocumentType {
constructor(directory) {
this.folderName = directory ? directory : 'files'
}
getFolderName() {
return this.folderName
}
}
module.exports = { ImageFile }
module.exports = { MediaFile }
87 changes: 87 additions & 0 deletions classes/MediaSubfolder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const _ = require('lodash')

const { File, ImageType, DocumentType } = require('./File.js')
const { getTree, sendTree } = require('../utils/utils.js')

class MediaSubfolder {
constructor(accessToken, siteName, fileType) {
this.accessToken = accessToken
this.siteName = siteName
switch (fileType) {
case 'images':
this.fileType = new ImageType()
this.mediaFolderName = fileType
break
case 'documents':
this.fileType = new DocumentType()
this.mediaFolderName = 'files'
break
default:
throw new Error("Invalid media type!")
}
}

async create(subfolderPath) {
try {
const IsomerFile = new File(this.accessToken, this.siteName)
IsomerFile.setFileType(this.fileType)
await IsomerFile.create(`${subfolderPath}/.keep`, '')
} catch (err) {
throw err
}
}

async delete(subfolderPath, currentCommitSha, treeSha) {
try {
const commitMessage = `Delete ${this.mediaFolderName} subfolder ${subfolderPath}`
const gitTree = await getTree(this.siteName, this.accessToken, treeSha, true)
const directoryName = `${this.mediaFolderName}/${subfolderPath}`
const newGitTree = []
gitTree.forEach(item => {
if (item.path.includes(directoryName)) {
return
} else if (item.type === 'tree' && item.path.includes(this.mediaFolderName)) {
// We don't include any trees in the media folder - we reconstruct them by adding all their individual files instead
return
} else {
newGitTree.push(item)
}
})
await sendTree(newGitTree, currentCommitSha, this.siteName, this.accessToken, commitMessage)
} catch (err) {
throw err
}
}

async rename(oldSubfolderPath, newSubfolderPath, currentCommitSha, treeSha) {
try {
const commitMessage = `Rename ${this.mediaFolderName} subfolder from ${oldSubfolderPath} to ${newSubfolderPath}`

const gitTree = await getTree(this.siteName, this.accessToken, treeSha, true);
const oldDirectoryName = `${this.mediaFolderName}/${oldSubfolderPath}`
const newDirectoryName = `${this.mediaFolderName}/${newSubfolderPath}`
const newGitTree = []
gitTree.forEach(item => {
if (item.path === oldDirectoryName) {
newGitTree.push({
...item,
path: newDirectoryName
})
} else if (item.path.includes(oldDirectoryName)) {
// We don't want to include these because they use the old path, they are included with the renamed tree
return
} else if (item.type === 'tree' && item.path.includes(this.mediaFolderName)) {
// We don't include any other trees - we reconstruct them by adding all their individual files instead
return
} else {
newGitTree.push(item)
}
})
await sendTree(newGitTree, currentCommitSha, this.siteName, this.accessToken, commitMessage);
} catch (err) {
throw err
}
}
}

module.exports = { MediaSubfolder }
21 changes: 20 additions & 1 deletion middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,24 @@ const verifyJwt = (req, res, next) => {
return next('router')
}

// Extracts access_token if any, else set access_token to null
const whoamiAuth = (req, res, next) => {
let access_token
try {
const { isomercms } = req.cookies
access_token = jwtUtils.verifyToken(isomercms).access_token
} catch (err) {
access_token = undefined
} finally {
req.accessToken = access_token
return next('router')
}
}

// Login and logout
auth.get('/v1/auth', noVerify)
auth.get('/v1/auth/logout', noVerify)
auth.get('/v1/auth/whoami', verifyJwt)
auth.get('/v1/auth/whoami', whoamiAuth)

// Index
auth.get('/v1', noVerify)
Expand Down Expand Up @@ -89,6 +103,11 @@ auth.post('/v1/sites/:siteName/images/:imageName', verifyJwt)
auth.delete('/v1/sites/:siteName/images/:imageName', verifyJwt)
auth.post('/v1/sites/:siteName/images/:imageName/rename/:newImageName', verifyJwt)

// Media subfolders
auth.post('/v1/sites/:siteName/media/:mediaType/:folderPath', verifyJwt)
auth.delete('/v1/sites/:siteName/media/:mediaType/:folderPath', verifyJwt)
auth.post('/v1/sites/:siteName/media/:mediaType/:oldFolderPath/rename/:newFolderPath', verifyJwt)

// Menu directory
auth.get('/v1/sites/:siteName/tree', verifyJwt)

Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"http-errors": "~1.6.3",
"js-base64": "^2.5.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.19",
"lodash": "^4.17.21",
"moment-timezone": "^0.5.31",
"morgan": "~1.9.1",
"query-string": "^6.8.3",
Expand Down
23 changes: 14 additions & 9 deletions routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,20 @@ async function whoami(req, res) {
// Make a call to github
const endpoint = 'https://api.github.com/user'

const resp = await axios.get(endpoint, {
headers: {
Authorization: `token ${accessToken}`,
"Content-Type": "application/json"
}
})

const { login: userId } = resp.data
res.status(200).json({ userId })
let userId
try {
const resp = await axios.get(endpoint, {
headers: {
Authorization: `token ${accessToken}`,
"Content-Type": "application/json"
}
})
userId = resp.data.login
} catch (err) {
userId = undefined
} finally {
res.status(200).json({ userId })
}
}

router.get('/', attachReadRouteHandlerWrapper(githubAuth));
Expand Down
7 changes: 6 additions & 1 deletion routes/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ async function listDirectoryContent (req, res, next) {
const IsomerDirectory = new Directory(accessToken, siteName)
const folderType = new FolderType(decodedPath)
IsomerDirectory.setDirType(folderType)
const directoryContents = await IsomerDirectory.list()
let directoryContents = []
try {
directoryContents = await IsomerDirectory.list()
} catch (err) {
console.log(err)
}
res.status(200).json({ directoryContents })
}

Expand Down
55 changes: 41 additions & 14 deletions routes/documents.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,32 @@ const router = express.Router();

// Import classes
const { File, DocumentType } = require('../classes/File.js');
const { MediaFile } = require('../classes/MediaFile.js');
const {
attachReadRouteHandlerWrapper,
attachWriteRouteHandlerWrapper
} = require('../middleware/routeHandler')

const extractDirectoryAndFileName = (documentName) => {
let documentDirectory, documentFileName

// documentName contains the file path excluding the media folder, e.g. subfolder1/subfolder2/file.pdf
const pathArr = documentName.split('/')
if (pathArr.length === 1) {
// documentName only contains the file name
documentDirectory = 'files'
documentFileName = documentName
} else if (pathArr.length > 1) {
// We discard the name of the file for the directory
documentDirectory = `files/${pathArr.slice(0, -1)}`
documentFileName = pathArr[pathArr.length - 1]
}
return {
documentDirectory,
documentFileName,
}
}

// List documents
async function listDocuments (req, res, next) {
const { accessToken } = req
Expand All @@ -26,15 +47,14 @@ async function createNewDocument (req, res, next) {
const { accessToken } = req

const { siteName } = req.params
const { documentName, content } = req.body
const { documentName, documentDirectory, content } = req.body

// TO-DO:
// Validate fileName and content

const IsomerFile = new File(accessToken, siteName)
const documentType = new DocumentType()
IsomerFile.setFileType(documentType)
const { sha } = await IsomerFile.create(documentName, content)
const IsomerDocumentFile = new MediaFile(accessToken, siteName)
IsomerDocumentFile.setFileTypeToDocument(documentDirectory)
const { sha } = await IsomerDocumentFile.create(documentName, content)

res.status(200).json({ documentName, content, sha })
}
Expand All @@ -44,10 +64,12 @@ async function readDocument (req, res, next) {
const { accessToken } = req
const { siteName, documentName } = req.params

const IsomerFile = new File(accessToken, siteName)
const documentType = new DocumentType()
IsomerFile.setFileType(documentType)
const { sha, content } = await IsomerFile.read(documentName)
// get document directory
const { documentDirectory, documentFileName } = extractDirectoryAndFileName(documentName)

const IsomerDocumentFile = new MediaFile(accessToken, siteName)
IsomerDocumentFile.setFileTypeToDocument(documentDirectory)
const { sha, content } = await IsomerDocumentFile.read(documentFileName)

// TO-DO:
// Validate content
Expand Down Expand Up @@ -98,11 +120,16 @@ async function renameDocument (req, res, next) {
// TO-DO:
// Validate documentName and content

const IsomerFile = new File(accessToken, siteName)
const documentType = new DocumentType()
IsomerFile.setFileType(documentType)
const { sha: newSha } = await IsomerFile.create(newDocumentName, content)
await IsomerFile.delete(documentName, sha)
const { documentDirectory: oldDocumentDirectory, documentFileName: oldDocumentFileName } = extractDirectoryAndFileName(documentName)
const { documentDirectory: newDocumentDirectory, documentFileName: newDocumentFileName } = extractDirectoryAndFileName(newDocumentName)

const newIsomerDocumentFile = new MediaFile(accessToken, siteName)
newIsomerDocumentFile.setFileTypeToDocument(newDocumentDirectory)
const { sha: newSha } = await newIsomerDocumentFile.create(newDocumentFileName, content)

const oldIsomerDocumentFile = new MediaFile(accessToken, siteName)
oldIsomerDocumentFile.setFileTypeToDocument(oldDocumentDirectory)
await oldIsomerDocumentFile.delete(oldDocumentFileName, sha)

res.status(200).json({ documentName: newDocumentName, content, sha: newSha })
}
Expand Down
Loading