Skip to content
This repository has been archived by the owner on Feb 8, 2023. It is now read-only.

Backup Prismic #77

Merged
merged 12 commits into from
Sep 6, 2016
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
dist/**
node_modules/**
**/node_modules
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ coverage
docs/.sass-cache
docs/_site
docs/Gemfile.lock

**/.serverless
2 changes: 2 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ dependencies:
pre:
- rm -rf node_modules
- npm install
- cd prismic-backup-service && npm install
- sudo pip install awsebcli --upgrade --ignore-installed six

test:
override:
- npm run lint
- npm run test-with-coverage
- cd prismic-backup-service && npm run test
post:
- cat ./coverage/lcov.info | ./node_modules/.bin/coveralls

Expand Down
8 changes: 2 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"start": "nodemon lib/index.js --exec babel-node",
"build": "babel lib -d dist",
"lint": "eslint lib/",
"lint": "eslint lib/ prismic-backup-service/",
"serve": "node dist/index.js",
"test": "mocha",
"test-watch": "mocha --watch --reporter min",
Expand Down Expand Up @@ -67,11 +67,7 @@
"pre-commit": [
"npm run lint",
"npm t"
],
"pre-push": [],
"post-commit": [],
"post-checkout": [],
"post-merge": []
]
}
}
}
12 changes: 12 additions & 0 deletions prismic-backup-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Prismic backup service
============

Lambda that requests documents from prismic and stores the results in an S3 bucket.

serverless 🙌

```
npm install serverless@1.0.0-rc.1 -g
sls deploy
sls invoke --function backupPrismic
```
21 changes: 21 additions & 0 deletions prismic-backup-service/bin/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'; // eslint-disable-line strict
const backup = require('../lib');
const variables = require('../config/variables.json');

const environment = (cb, functionName) => {
switch (functionName) {
case 'prismic-backup-service-dev-backupPrismic':
return variables.dev;
case 'prismic-backup-service-prod-backupPrismic':
return variables.prod;
default:
return cb('Unable to determine environment.');
}
};

module.exports.backupPrismic = (event, context, cb) => {
const env = environment(cb, context.functionName);
backup(env.bucketName)
.then(metadata => cb(null, metadata))
.catch(err => cb(err));
};
8 changes: 8 additions & 0 deletions prismic-backup-service/config/variables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"dev": {
"bucketName": "prismic-backup-dev"
},
"prod": {
"bucketName": "prismic-backup-prod"
}
}
54 changes: 54 additions & 0 deletions prismic-backup-service/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const fetch = require('node-fetch');
const leftPad = require('./left-pad');
const saveJson = require('./s3').saveJson;

const prismicURL = 'https://rb-website-stage.prismic.io/api/documents/search?ref=V80_SyMAAKhGWsDT&page=1&pageSize=100';
const timestamp = new Date().toISOString().substring(0, 10);

function saveMetadata(bucketName, metadata, funcs) {
const name = `${timestamp}-prismic-backup/metadata.json`;
return funcs.saveJson(bucketName, name, metadata);
}

function savePrismicData(bucketName, json, funcs) {
const name = `${timestamp}-prismic-backup/page-${leftPad(json.page, 3)}.json`;
return funcs.saveJson(bucketName, name, json);
}

function updateMetadata(metadata, data) {
return {
totalDocuments: data.total_results_size,
seenDocuments: (metadata.seenDocuments || 0) + data.results.length,
totalPages: data.total_pages,
date: timestamp,
};
}

function getJson(url) {
return fetch(url)
.then(res => res.json());
}

function loop(bucketName, json, metadata, funcs) {
return savePrismicData(bucketName, json, funcs).then(() => {
const newMetadata = updateMetadata(metadata, json);

if (json.next_page) {
return funcs.getJson(json.next_page)
.then((newJson) => loop(bucketName, newJson, newMetadata, funcs));
}
return newMetadata;
});
}

const defaultFuncs = {
getJson,
saveJson,
};

module.exports = function backupPrismic(bucketName, passedFuncs) {
const funcs = passedFuncs || defaultFuncs;
return funcs.getJson(prismicURL)
.then(json => loop(bucketName, json, {}, funcs))
.then(metadata => saveMetadata(bucketName, metadata, funcs));
};
70 changes: 70 additions & 0 deletions prismic-backup-service/lib/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const backupPrismic = require('./');
const expect = require('chai').expect;
const describe = require('mocha').describe;
const it = require('mocha').it;

function okPromise(fn) {
return new Promise(resolve => resolve(fn()));
}

describe('backupPrismic', () => {
it('it saves all pages and metadata', (done) => {
const responses = [
{
page: 1,
results_per_page: 100,
results_size: 100,
total_results_size: 132,
total_pages: 2,
next_page: 'https://r.prismic.io/api/documents/search?ref=V80&page=2&pageSize=100',
prev_page: null,
results: [
'we have results 1',
],
},
{
page: 2,
results_per_page: 100,
results_size: 32,
total_results_size: 132,
total_pages: 2,
next_page: null,
prev_page: 'https://r.prismic.io/api/documents/search?ref=V80&page=1&pageSize=100',
results: [
'we have results 2',
],
},
];
const responsesCopy = Object.assign({}, responses);
const saved = [];
const getJson = () => okPromise(() => responses.shift());
const saveJson = (bucketName, name, data) => okPromise(() => {
saved.push({ bucketName, name, data });
});
const funcs = { getJson, saveJson };
backupPrismic('bucketNameForTesting', funcs)
.then(() => {
expect(saved.length).to.equal(3, 'Expected 3 items to have been saved to S3');

expect(saved[0].name).to.match(/....-..-..-prismic-backup\/page-001.json/);
expect(saved[1].name).to.match(/....-..-..-prismic-backup\/page-002.json/);
expect(saved[2].name).to.match(/....-..-..-prismic-backup\/metadata.json/);

expect(saved[0].bucketName).to.equal('bucketNameForTesting');
expect(saved[1].bucketName).to.equal('bucketNameForTesting');
expect(saved[2].bucketName).to.equal('bucketNameForTesting');

expect(saved[0].data).to.deep.equal(responsesCopy[0]);
expect(saved[1].data).to.deep.equal(responsesCopy[1]);

const metadata = saved[2].data;
expect(metadata.totalDocuments).to.equal(132);
expect(metadata.seenDocuments).to.equal(2);
expect(metadata.totalPages).to.equal(2);
expect(metadata.date).to.match(/....-..-../);

done();
})
.catch(done);
});
});
9 changes: 9 additions & 0 deletions prismic-backup-service/lib/left-pad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'; // eslint-disable-line strict

module.exports = function leftPad(number, targetLength) {
let output = number.toString();
while (output.length < targetLength) {
output = `0${output}`;
}
return output;
};
24 changes: 24 additions & 0 deletions prismic-backup-service/lib/s3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function saveJson(bucketName, key, data) {
const AWS = require('aws-sdk'); // eslint-disable-line import/no-unresolved, global-require

AWS.config.region = 'eu-west-1';

const s3bucket = new AWS.S3({ params: { Bucket: bucketName } });

const index = {
Key: key,
Body: JSON.stringify(data),
ContentType: 'application/json',
};
return new Promise((resolve, reject) => {
s3bucket.upload(index, (err) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}

module.exports = { saveJson };
24 changes: 24 additions & 0 deletions prismic-backup-service/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "prismic-backup-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha",
"test-watch": "mocha --watch --reporter min"

},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"node-fetch": "^1.6.0"
},
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^3.0.2"
},
"config": {
"pre-git": {
}
}
}
20 changes: 20 additions & 0 deletions prismic-backup-service/serverless.env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This is the Serverless Environment File
#
# It contains listing of your stages and their regions
# It also manages serverless variables at 3 levels:
# - common variables: variables that apply to all stages/regions
# - stage variables: variables that apply to a specific stage
# - region variables: variables that apply to a specific region

vars:
stages:
dev:
vars:
regions:
eu-west-1:
vars:
prod:
vars:
regions:
eu-west-1:
vars:
39 changes: 39 additions & 0 deletions prismic-backup-service/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
service: prismic-backup-service # NOTE: update this with your service name

custom:
bucketName: prismic-backup-dev #${file(./config/variables.json)}

provider:
name: aws
stage: dev
region: eu-west-1
runtime: nodejs4.3
iamRoleStatements:
- Effect: "Allow"
Action:
- "s3:ListBucket"
- "s3:PutObject"
Resource:
- "arn:aws:s3:::${self:custom.bucketName}"
- "arn:aws:s3:::${self:custom.bucketName}/*"

functions:
backupPrismic:
handler: bin/handler.backupPrismic

# you can add any of the following events
# events:
# - http:
# path: users/create
# method: get
# - s3: ${bucket}
# - schedule: rate(10 minutes)
# - sns: greeter-topic

# you can add CloudFormation resource templates here
resources:
Resources:
BackupBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.bucketName}
1 change: 1 addition & 0 deletions prismic-backup-service/test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./{lib,test}/**/*spec.js
2 changes: 1 addition & 1 deletion test/mocha.opts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
--require test/helpers/globals
--require babel-polyfill
---reporter mocha-trumpet-reporter
./{,!(node_modules|dist)/**/}*spec.js
./{lib,test}/**/*spec.js