closes #33 - api example

Moving towards using an API which we can both expose publicly, and use internally.
Due to issues with JugglingDB, this breaks updating contentHTML on edit
Also, language, status, featured etc are all no long set / updated.
This commit is contained in:
Hannah Wolfe 2013-05-16 12:21:13 +01:00
parent e7b37f8671
commit bb6880ea49
7 changed files with 129 additions and 78 deletions

13
app.js
View File

@ -6,9 +6,9 @@
// Module dependencies.
var express = require('express'),
fs = require('fs'),
admin = require('./core/admin/controllers'),
frontend = require('./core/frontend/controllers'),
api = require('./core/shared/api'),
flash = require('connect-flash'),
Ghost = require('./core/ghost'),
I18n = require('./core/lang/i18n'),
@ -46,11 +46,13 @@
/**
* API routes..
* @todo convert these into a RESTful, public, authenticated API!
* @todo auth should be public auth not user auth
*/
ghost.app().post('/api/v0.1/posts/create', auth, admin.posts.create);
ghost.app().post('/api/v0.1/posts/edit', auth, admin.posts.edit);
ghost.app().get('/api/v0.1/posts', auth, admin.posts.index);
ghost.app().get('/api/v0.1/posts', auth, api.requestHandler(api.posts.browse));
ghost.app().get('/api/v0.1/posts/:id', auth, api.requestHandler(api.posts.read));
ghost.app().post('/api/v0.1/posts/create', auth, api.requestHandler(api.posts.add));
ghost.app().put('/api/v0.1/posts/edit', auth, api.requestHandler(api.posts.edit));
ghost.app()['delete']('/api/v0.1/posts/:id', auth, api.requestHandler(api.posts.destroy));
/**
* Admin routes..
@ -75,6 +77,5 @@
ghost.app().listen(3333, function () {
console.log("Express server listening on port " + 3333);
console.log('process: ', process.env);
});
}());

View File

@ -56,7 +56,7 @@
function save() {
var entry = {
title: document.getElementById('entry-title').value,
markdown: editor.getValue()
content: editor.getValue()
},
urlSegments = window.location.pathname.split('/');
@ -64,7 +64,7 @@
entry.id = urlSegments[3];
$.ajax({
url: '/api/v0.1/posts/edit',
method: 'POST',
method: 'PUT',
data: entry,
success: function (data) {
console.log('response', data);

View File

@ -1,11 +1,12 @@
/*global require, module */
(function () {
"use strict";
var Ghost = require('../../ghost'),
_ = require('underscore'),
fs = require('fs'),
Showdown = require('showdown'),
converter = new Showdown.converter(),
when = require('when/node/function'),
api = require('../../shared/api'),
ghost = new Ghost(),
adminNavbar,
@ -60,14 +61,15 @@
},
'editor': function (req, res) {
if (req.params.id !== undefined) {
ghost.dataProvider().posts.findOne({'id': parseInt(req.params.id, 10)}, function (error, post) {
res.render('editor', {
bodyClass: 'editor',
adminNav: setSelected(adminNavbar, 'blog'),
title: post.title,
content: post.content
api.posts.read(parseInt(req.params.id, 10))
.then(function (post) {
res.render('editor', {
bodyClass: 'editor',
adminNav: setSelected(adminNavbar, 'blog'),
title: post.title,
content: post.content
});
});
});
} else {
res.render('editor', {
bodyClass: 'editor',
@ -76,13 +78,14 @@
}
},
'blog': function (req, res) {
ghost.dataProvider().posts.findAll(function (error, posts) {
res.render('blog', {
bodyClass: 'manage',
adminNav: setSelected(adminNavbar, 'blog'),
posts: posts
api.posts.browse()
.then(function (posts) {
res.render('blog', {
bodyClass: 'manage',
adminNav: setSelected(adminNavbar, 'blog'),
posts: posts
});
});
});
},
'settings': function (req, res) {
res.render('settings', {
@ -119,47 +122,6 @@
res.redirect('/ghost/debug');
});
}
},
'posts': {
'index': function (req, res) {
},
'create': function (req, res) {
var entry = {
title: req.body.title,
content: req.body.markdown,
contentHtml: '',
language: ghost.config().defaultLang,
status: ghost.statuses().draft,
featured: false
};
entry.contentHtml = converter.makeHtml(entry.content);
ghost.dataProvider().posts.add(entry, function (error, post) {
if (!error) {
console.log('added', post);
res.json({id: post.id});
} else {
res.json(400, {error: post.errors});
}
});
},
'edit': function (req, res) {
var entry = {
id: parseInt(req.body.id, 10),
title: req.body.title,
content: req.body.markdown,
contentHtml: ''
};
entry.contentHtml = converter.makeHtml(entry.content);
ghost.dataProvider().posts.edit(entry, function (error, post) {
console.log('edited', post);
res.json({id: parseInt(post.id, 10)});
});
}
}
};

77
core/shared/api.js Normal file
View File

@ -0,0 +1,77 @@
// # Ghost Data API
// Provides access to the data model
/**
* This is intended to replace the old dataProvider files and should access & manipulate the models directly
*/
/*global module, require */
(function () {
"use strict";
var Ghost = require('../ghost'),
when = require('when/node/function'),
_ = require('underscore'),
ghost = new Ghost(),
posts,
users,
requestHandler;
// # Posts
posts = {
// takes filter / pagination parameters
// returns a list of posts in a json response
browse: function (options) {
return when.call(ghost.dataProvider().posts.findAll);
},
// takes an identifier (id or slug?)
// returns a single post in a json response
read: function (id) {
return when.call(ghost.dataProvider().posts.findOne, {id: id});
},
// takes a json object with all the properties which should be updated
// returns the resulting post in a json response
edit: function (postData) {
console.log('edit data', postData);
return when.call(ghost.dataProvider().posts.edit, postData);
},
// takes a json object representing a post,
// returns the resulting post in a json response
add: function (postData) {
console.log('data', postData);
return when.call(ghost.dataProvider().posts.add, postData);
},
// takes an identifier (id or slug?)
// returns a json response with the id of the deleted post
destroy: function (id) {
return when.call(ghost.dataProvider().posts.destroy, id);
}
};
// # Users
users = {};
// settings: {},
// categories: {},
// post_categories: {}
// requestHandler
// decorator for api functions which are called via an HTTP request
// takes the API method and wraps it so that it gets data from the request and returns a sensible JSON response
requestHandler = function (apiMethod) {
return function (req, res) {
var options = _.extend(req.body, req.params);
return apiMethod(options).then(function (result) {
res.json(result);
}, function (error) {
res.json(400, {error: error});
});
};
};
module.exports.posts = posts;
module.exports.users = users;
module.exports.requestHandler = requestHandler;
}());

View File

@ -101,6 +101,7 @@
/**
* Naive find one where args match
* @param args
* @param callback
*/
DataProvider.prototype.posts.findOne = function (args, callback) {
@ -109,7 +110,7 @@
/**
* Naive add
* @param post
* @param _post
* @param callback
*/
DataProvider.prototype.posts.add = function (_post, callback) {
@ -122,7 +123,7 @@
/**
* Naive edit
* @param post
* @param _post
* @param callback
*/
DataProvider.prototype.posts.edit = function (_post, callback) {
@ -133,6 +134,13 @@
});
};
DataProvider.prototype.posts.destroy = function (_identifier, callback) {
schema.models.Post.findOne({where: {id: _identifier}}, function (error, post) {
schema.models.Post.destroy(post.id, callback);
});
};
DataProvider.prototype.populateData = populateData;
module.exports = DataProvider;

View File

@ -4,7 +4,7 @@
* Vastly incomplete!
*/
/*globals module, require */
/*global module, require */
(function () {
"use strict";
@ -12,6 +12,8 @@
schema = new Schema('sqlite3', {
database: __dirname + '/../data/datastore.db'
}),
Showdown = require('showdown'),
converter = new Showdown.converter(),
Post,
User,
Setting;
@ -39,16 +41,15 @@
};
Post.prototype.preCreate = function (next) {
console.log('pre create 1', this);
//console.log('pre create 1', this);
if (this.createdAt === undefined) {
this.createdAt = new Date();
}
if (this.slug === undefined) {
this.slug = this.generateSlug();
}
this.createdAt = this.createdAt || new Date();
this.slug = this.slug || this.generateSlug();
// this.language = this.language || ghost.config().defaultLang;
// this.status = this.status || ghost.statuses().draft
this.featured = false;
console.log('pre create 2', this);
// console.log('pre create 2', this);
next();
};
@ -59,11 +60,12 @@
//Post.validatesUniquenessOf('slug');
//Post.validatesLengthOf('language', {min: 2, max: 5}, "The language code should be between 2 and 5 chars long, E.g. 'en' or 'en_GB' ");
Post.beforeSave = function (next, data) {
// doesn't get run on update
Post.beforeSave = Post.beforeUpdate = function (next, data) {
console.log('before s1', data);
// set updated
data.updatedAt = new Date();
data.contentHtml = converter.makeHtml(data.content);
next();
};

View File

@ -15,8 +15,9 @@
"moment": "*",
"underscore": "*",
"showdown": "*",
"when": "*",
"sqlite3": "2.1.7",
"jugglingdb": "0.2.x",
"jugglingdb": "0.2.0-29",
"jugglingdb-sqlite3": "git+https://github.com/jugglingdb/sqlite3-adapter.git#master"
},
"devDependencies": {