Skip to content

Commit

Permalink
feat: make needing a database optional. fixes #264
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Aug 26, 2020
1 parent c455c7d commit 36cb470
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 58 deletions.
3 changes: 1 addition & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"required": true
},
"MONGODB_URL": {
"description": "URL of a MongoDB host",
"required": true
"description": "URL of a MongoDB host (optional)"
},
"NPM_CONFIG_PRODUCTION": {
"descrition": "Skip pruning devDependencies while packages are not public",
Expand Down
1 change: 1 addition & 0 deletions indiekit.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ indiekit.set('application.locale', process.env.LOCALE);
// Publication settings
indiekit.set('publication.me', process.env.PUBLICATION_URL);
indiekit.set('publication.storeId', 'github');
indiekit.set('publication.config.categories.url', 'http://paulrobertlloyd.com/categories/index.json');

// Server
const server = indiekit.server();
Expand Down
17 changes: 11 additions & 6 deletions packages/endpoint-media/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export const MediaEndpoint = class {
init(indiekitConfig) {
const {application, publication} = indiekitConfig;

indiekitConfig.addNavigation({
href: `${this.mountpath}/files`,
text: 'Files'
});
if (application.hasDatabase) {
indiekitConfig.addNavigation({
href: `${this.mountpath}/files`,
text: 'Files'
});
}

indiekitConfig.addRoute({
mountpath: this.mountpath,
Expand All @@ -51,8 +53,11 @@ export const MediaEndpoint = class {

this._router.get('/', queryController(publication));
this._router.post('/', indieauth(publication), multipartParser.single('file'), uploadController(publication));
this._router.get('/files', authenticate, filesController(publication).list);
this._router.get('/files/:id', authenticate, filesController(publication).view);

if (application.hasDatabase) {
this._router.get('/files', authenticate, filesController(publication).list);
this._router.get('/files/:id', authenticate, filesController(publication).view);
}

return router;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/endpoint-media/lib/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export const media = {
if (uploaded) {
mediaData.date = new Date();
mediaData.lastAction = 'upload';
await media.insertOne(mediaData);

if (media) {
await media.insertOne(mediaData);
}

return {
location: mediaData.properties.url,
status: 201,
Expand Down
17 changes: 11 additions & 6 deletions packages/endpoint-micropub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export const MicropubEndpoint = class {
init(indiekitConfig) {
const {application, publication} = indiekitConfig;

indiekitConfig.addNavigation({
href: `${this.mountpath}/posts`,
text: 'Posts'
});
if (application.hasDatabase) {
indiekitConfig.addNavigation({
href: `${this.mountpath}/posts`,
text: 'Posts'
});
}

indiekitConfig.addRoute({
mountpath: this.mountpath,
Expand All @@ -51,8 +53,11 @@ export const MicropubEndpoint = class {

this._router.get('/', queryController(publication));
this._router.post('/', indieauth(publication), multipartParser.any(), actionController(publication));
this._router.get('/posts', authenticate, postsController(publication).list);
this._router.get('/posts/:id', authenticate, postsController(publication).view);

if (application.hasDatabase) {
this._router.get('/posts', authenticate, postsController(publication).list);
this._router.get('/posts/:id', authenticate, postsController(publication).view);
}

return router;
}
Expand Down
32 changes: 22 additions & 10 deletions packages/endpoint-micropub/lib/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export const post = {
if (published) {
postData.date = new Date();
postData.lastAction = 'create';
await posts.insertOne(postData);

if (posts) {
await posts.insertOne(postData);
}

return {
location: postData.properties.url,
Expand Down Expand Up @@ -55,9 +58,12 @@ export const post = {
if (published) {
postData.date = new Date();
postData.lastAction = 'update';
await posts.replaceOne({
url: postData.properties.url
}, postData);

if (posts) {
await posts.replaceOne({
url: postData.properties.url
}, postData);
}

const hasUpdatedUrl = (url !== postData.properties.url);
return {
Expand Down Expand Up @@ -92,9 +98,12 @@ export const post = {
if (published) {
postData.date = new Date();
postData.lastAction = 'delete';
await posts.replaceOne({
url: postData.properties.url
}, postData);

if (posts) {
await posts.replaceOne({
url: postData.properties.url
}, postData);
}

return {
status: 200,
Expand Down Expand Up @@ -131,9 +140,12 @@ export const post = {
if (published) {
postData.date = new Date();
postData.lastAction = 'undelete';
await posts.replaceOne({
url: postData.properties.url
}, postData);

if (posts) {
await posts.replaceOne({
url: postData.properties.url
}, postData);
}

return {
location: postData.properties.url,
Expand Down
1 change: 1 addition & 0 deletions packages/indiekit/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const defaultConfig = {
themeColor: '#0000ee',
repository: package_.repository,
version: package_.version,
hasDatabase: typeof process.env.MONGODB_URL !== 'undefined',
endpoints: [
mediaEndpoint,
micropubEndpoint,
Expand Down
17 changes: 10 additions & 7 deletions packages/indiekit/config/mongodb.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import mongodb from 'mongodb';

export const mongodbConfig = (async () => {
const {MongoClient} = mongodb;
const client = new MongoClient(process.env.MONGODB_URL, {
useUnifiedTopology: true
});
await client.connect();
const database = client.db('indiekit');
return database;
const mongodbUrl = process.env.MONGODB_URL;
if (mongodbUrl) {
const {MongoClient} = mongodb;
const client = new MongoClient(mongodbUrl, {
useUnifiedTopology: true
});
await client.connect();
const database = client.db('indiekit');
return database;
}
})();
13 changes: 8 additions & 5 deletions packages/indiekit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,19 @@ export const Indiekit = class {
}

async init() {
const {locale, presets, stores} = this.application;
const {hasDatabase, locale, presets, stores} = this.application;
const {config, presetId, storeId} = this.publication;
const database = await mongodbConfig;

// Application cache collection
this.application.cache = hasDatabase ? await database.collection('cache') : false;

// Publication data collections
const database = await mongodbConfig;
this.publication.posts = await database.collection('posts');
this.publication.media = await database.collection('media');
this.publication.posts = hasDatabase ? await database.collection('posts') : false;
this.publication.media = hasDatabase ? await database.collection('media') : false;

// Publication configuration
const cache = new Cache(mongodbConfig);
const cache = new Cache(this.application.cache);
const preset = getPreset(presets, presetId);
const categories = await getCategories(cache, config.categories);
this.publication.preset = preset;
Expand Down
22 changes: 11 additions & 11 deletions packages/indiekit/lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import got from 'got';
export const Cache = class {
/** Fetch data from cache or remote file
*
* @param {object} database Database
* @param {string} expires Timeout on key
* @param {object} collection Collection
*/
constructor(database, expires) {
this.database = database;
this.expires = expires || 3600;
constructor(collection) {
this.collection = collection;
}

/**
Expand All @@ -20,9 +18,7 @@ export const Cache = class {
*/
async json(key, url) {
try {
const database = await this.database;
const collection = await database.collection('cache');
const cachedData = await collection.findOne({key, url});
const cachedData = this.collection ? await this.collection.findOne({key, url}) : false;
if (cachedData) {
const {data} = cachedData;
return {
Expand All @@ -34,9 +30,13 @@ export const Cache = class {
const fetchedData = await got(url, {responseType: 'json'});
if (fetchedData) {
const data = fetchedData.body;
await collection.replaceOne({}, {key, url, data}, {
upsert: true
});

if (this.collection) {
await this.collection.replaceOne({}, {key, url, data}, {
upsert: true
});
}

return {
source: url,
data
Expand Down
18 changes: 9 additions & 9 deletions packages/indiekit/tests/lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import nock from 'nock';
import {mongodbConfig} from '../../config/mongodb.js';
import {Cache} from '../../lib/cache.js';

const cache = new Cache(mongodbConfig);
test.beforeEach(async t => {
const database = await mongodbConfig;
const collection = await database.collection('cache');

test.beforeEach(t => {
t.context = {
cache: new Cache(collection),
nock: nock('https://website.example').get('/categories.json'),
url: 'https://website.example/categories.json'
};
Expand All @@ -19,25 +21,23 @@ test.afterEach.always(async () => {

test.serial('Returns data from remote file and saves to cache', async t => {
const scope = t.context.nock.reply(200, ['Foo', 'Bar']);
const result = await cache.json('category', t.context.url);
const result = await t.context.cache.json('category', t.context.url);
t.is(result.source, t.context.url);
scope.done();
});

test.serial('Throws error if remote file not found', async t => {
const scope = t.context.nock.replyWithError('Not found');
const error = await t.throwsAsync(cache.json('file', t.context.url));
const error = await t.throwsAsync(t.context.cache.json('file', t.context.url));
t.is(error.message, `Unable to fetch ${t.context.url}: Not found`);
scope.done();
});

test.serial('Gets data from cache', async t => {
const cache = new Cache({
collection: () => ({
findOne: async () => ({
souce: 'cache',
date: {}
})
findOne: async () => ({
souce: 'cache',
date: {}
})
});

Expand Down
5 changes: 4 additions & 1 deletion packages/indiekit/tests/lib/publication.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import {
} from '../../lib/publication.js';

test.beforeEach(async t => {
const database = await mongodbConfig;
const collection = await database.collection('cache');

t.context = await {
cache: new Cache(mongodbConfig),
cache: new Cache(collection),
categories: {
nock: nock('https://website.example').get('/categories.json'),
url: 'https://website.example/categories.json'
Expand Down

0 comments on commit 36cb470

Please sign in to comment.