Skip to content

Commit

Permalink
Setup routes (#2)
Browse files Browse the repository at this point in the history
* Set up the routes layer for the backend server

* add first draft of Pages routes

* add first draft of Files and Images routes

* fix typo in commit messages

* add CollectionPages and ResourcePages routes

* rename files routes to documents to minimize confusion

* ensure that all ages routes endpoints work

* refactor routes layer into File class + use Strategy pattern

* clean up + add list pages for Collection and Resource routes

* make routes functional

* remove console log

* add GET collections route

* remove collections reorder endpoint

* lint file

* set up Config class

* add navigation routes

* update collections routes

* create a Collection class + refactor Config

* move endpoint to list pages in collection to collectionPages routes

* remove hbs view engine

* return empty object if folder is not found

* add autogenerated permalink to collections

* add cors module

* fix typo in documents routes

* fix typo + ensure that routes return a response
  • Loading branch information
prestonlimlianjie authored Oct 16, 2019
1 parent 99d429c commit 69f70f7
Show file tree
Hide file tree
Showing 23 changed files with 1,354 additions and 300 deletions.
123 changes: 123 additions & 0 deletions classes/Collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const yaml = require('js-yaml')
const base64 = require('base-64')
const Bluebird = require('bluebird')
const _ = require('lodash')

const { Config } = require('./Config.js')
const { File, CollectionPageType } = require('./File.js')

class Collection {
constructor(accessToken, siteName) {
this.accessToken = accessToken
this.siteName = siteName
}

async list() {
try {
const config = new Config(this.accessToken, this.siteName)
const { content, sha } = await config.read()
const contentObject = yaml.safeLoad(base64.decode(content))

return Object.keys(contentObject.collections)

} catch (err) {
throw err
}
}

async create(collectionName) {
try {
const config = new Config(this.accessToken, this.siteName)
const { content, sha } = await config.read()
const contentObject = yaml.safeLoad(base64.decode(content))

// TO-DO: Verify that collection doesn't already exist

contentObject.collections[`${collectionName}`] = {
permalink: '/:collection/:path/:title',
output: true
}
const newContent = base64.encode(yaml.safeDump(contentObject))

await config.update(newContent, sha)

} catch (err) {
throw err
}
}

async delete(collectionName) {
try {
// Delete collection in config
const config = new Config(this.accessToken, this.siteName)
const { content, sha } = await config.read()
const contentObject = yaml.safeLoad(base64.decode(content))

delete contentObject.collections[`${collectionName}`]
const newContent = base64.encode(yaml.safeDump(contentObject))

await config.update(newContent, sha)

// Get all collectionPages
const GitHubFile = new File(this.accessToken, this.siteName)
const collectionPageType = new CollectionPageType(collectionName)
GitHubFile.setFileType(collectionPageType)
const collectionPages = await GitHubFile.list()

// Delete all collectionPages
await Bluebird.map(collectionPages, async(collectionPage) => {
let pageName = collectionPage.pageName
const { sha } = await GitHubFile.read(pageName)
return GitHubFile.delete(pageName, sha)
})

} catch (err) {
throw err
}
}

async rename(oldCollectionName, newCollectionName) {
try {
// Rename collection in config
const config = new Config(this.accessToken, this.siteName)
const { content, sha } = await config.read()
const contentObject = yaml.safeLoad(base64.decode(content))

contentObject.collections[`${newCollectionName}`] = {
permalink: '/:collection/:path/:title',
output: true
}
delete contentObject.collections[`${oldCollectionName}`]
const newContent = base64.encode(yaml.safeDump(contentObject))

await config.update(newContent, sha)

// Get all collectionPages
const OldGitHubFile = new File(this.accessToken, this.siteName)
const oldCollectionPageType = new CollectionPageType(oldCollectionName)
OldGitHubFile.setFileType(oldCollectionPageType)
const collectionPages = await OldGitHubFile.list()

// If the object is empty (there are no pages in the collection), do nothing
if (_.isEmpty(collectionPages)) return

// Set up new collection File instance
const NewGitHubFile = new File(this.accessToken, this.siteName)
const newCollectionPageType = new CollectionPageType(newCollectionName)
NewGitHubFile.setFileType(newCollectionPageType)

// Rename all collectionPages
await Bluebird.map(collectionPages, async(collectionPage) => {
let pageName = collectionPage.fileName
const { content, sha } = await OldGitHubFile.read(pageName)
await OldGitHubFile.delete(pageName, sha)
return NewGitHubFile.create(pageName, content)
})

} catch (err) {
throw err
}
}
}

module.exports = { Collection }
63 changes: 63 additions & 0 deletions classes/Config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const axios = require('axios');

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 Config {
constructor(accessToken, siteName) {
this.accessToken = accessToken
this.siteName = siteName
}

async read() {
try {
const endpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/_config.yml`

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

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(newContent, sha) {
try {
const endpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/_config.yml`

let params = {
"message": 'Edit config',
"content": newContent,
"branch": "staging",
"sha": sha
}

await axios.put(endpoint, params, {
headers: {
Authorization: `token ${this.accessToken}`,
"Content-Type": "application/json"
}
})
} catch (err) {
throw err
}
}
}

module.exports = { Config }
197 changes: 197 additions & 0 deletions classes/File.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
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 File {
constructor(accessToken, siteName) {
this.accessToken = accessToken
this.siteName = siteName
this.baseEndpoint = null
}

setFileType(fileType) {
const folderPath = fileType.getFolderName()
this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/${folderPath}`
}

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
}
}
})

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",
}

await axios.put(endpoint, params, {
headers: {
Authorization: `token ${this.accessToken}`,
"Content-Type": "application/json"
}
})
} catch (err) {
throw err
}
}

async read(fileName) {
try {
const endpoint = `${this.baseEndpoint}/${fileName}`

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

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
}

await axios.put(endpoint, params, {
headers: {
Authorization: `token ${this.accessToken}`,
"Content-Type": "application/json"
}
})
} 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 PageType {
constructor() {
this.folderName = 'pages'
}
getFolderName() {
return this.folderName
}
}

class CollectionPageType {
constructor(collectionName) {
this.folderName = `_${collectionName}`
}
getFolderName() {
return this.folderName
}
}

class ResourcePageType {
constructor(resourceName) {
this.folderName = `_${resourceName}`
}
getFolderName() {
return this.folderName
}
}

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

class DocumentType {
constructor() {
this.folderName = 'files'
}
getFolderName() {
return this.folderName
}
}

class DataType {
constructor() {
this.folderName = '_data'
}
getFolderName() {
return this.folderName
}
}

module.exports = { File, PageType, CollectionPageType, ResourcePageType, ImageType, DocumentType, DataType }
Loading

0 comments on commit 69f70f7

Please sign in to comment.