Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 3 commits
  • 8 files changed
  • 0 commit comments
  • 1 contributor
View
4 fixtures/foobar_instance.js
@@ -43,21 +43,25 @@ exports.organisations = [
exports.positions = [
{
+ "_id": id("4f9ea1326e8770d854c45a22"),
title: "President",
person: id("4f9ea1326e8770d854c45a1d"), // george-bush
organisation: id("4f9ea1326e8770d854c45a21"),
},
{
+ "_id": id("4f9ea1326e8770d854c45a23"),
title: "President",
person: id("4f9ea1326e8770d854c45a1e"), // bill-clinton
organisation: id("4f9ea1326e8770d854c45a21"),
},
{
+ "_id": id("4f9ea1326e8770d854c45a24"),
title: "President",
person: id("4f9ea1326e8770d854c45a1f"), // george-w-bush
organisation: id("4f9ea1326e8770d854c45a21"),
},
{
+ "_id": id("4f9ea1326e8770d854c45a25"),
title: "President",
person: id("4f9ea1326e8770d854c45a20"), // barack-obama
organisation: id("4f9ea1326e8770d854c45a21"),
View
2  instance-app/app.js
@@ -131,7 +131,7 @@ app.configure(function(){
next();
});
- app.use('/api/v1', require('../lib/apps/api') );
+ app.use('/api', require('../lib/apps/api') );
app.use('/info', require('../lib/apps/info') );
app.use('/token', require('../lib/apps/token') );
View
93 instance-app/views/api/index.jade
@@ -0,0 +1,93 @@
+extend ../layout
+
+block title
+ | API
+
+block content
+ .page-header
+ h1 API
+
+ p The API allows you to easily access all the data stored in PopIt from your code or other websites.
+
+ h2 Getting Started
+
+ p
+ | The API returns
+ a(href="http://en.wikipedia.org/wiki/JSON") JSON
+ | - a data format that is easily usable in all programming languages. It is possible to view JSON in your browser (and easily follow embedded links) if you have one of the following extensions installed:
+
+ ul
+ li
+ a(href="https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc") JSONView
+ | for Chrome
+ li
+ a(href="https://addons.mozilla.org/en-US/firefox/addon/jsonview/") JSONView
+ | for FireFox
+ li
+ a(href="http://stackoverflow.com/questions/2483771/how-can-i-convince-ie-to-simply-display-application-json-rather-than-offer-to-do") Not sure it can be done easily
+ | for Internet Explorer :(
+ li Try a search for "JSON Viewer <your browser name>" for other browsers.
+
+ p
+ | You can now
+ a(href="/api/v1") enter the API
+ | and click around to navigate.
+
+ h2 Basics
+
+ p The API is based on
+ a(href="http://en.wikipedia.org/wiki/Representational_state_transfer") REST
+
+ p Currently the API is read only (only 'GET' requests supported) and likely to change as it undergoes development. Adding and editing data via the API will be added soon.
+
+
+ h3 API Versions
+
+ p There is a version string in the API url. This will be updated if the API undergoes siginificant changes that breaks backwards compatability. Old APIs will be supported for a bit, but will eventually be deprecated. Hopefully version changes will be very rare.
+
+ p
+ b NOTE
+ | until PopIt is considered stable the API will change and the 'v1' version will break. Please contact us if you need to know which parts of the API are stable.
+
+
+ h3 result / results
+
+ p When you make a query the returned data will always be a hash. There will be a result or results key that contains the data you've requested.
+
+
+ h3 meta
+
+ p When you make requests to the API you'll often get little 'meta' blocks in the results. These blocks are added to the data by the API to provide additional information that may be helpful. For example it might contain links to the API endpoint for a result in a search, or a link to the web page where you can edit the data.
+
+ p The meta blocks are also used to provide next and previous links in list results (not implemented yet).
+
+
+ h3 errors
+
+ p If something goes wrong there will be an 'error' or 'errors' key that will explain the error. Often an HTTP error code will also be used (eg '404' if a result cannot be found).
+
+
+ h3 ObjectIds and slugs
+
+ p Objects in PopIt are always identified in the API by their ObjectId - which is generated by the database and is a 24 character hex string.
+
+ p As a conveniance you can also look up objects using their slug. If found the request will be redirected to the correct ObjectId based query. This is so that you can use the slugs in URLs on your site and easily retrieve data without having to store the correspending ObjectIds locally.
+
+ pre
+ | by ObjectId: /api/v1/person/4f99219333fa2efc68000006
+ | by slug: /api/v1/person/joe-bloggs
+
+ p Note that slugs may change so your links may break. ObjectIds will never change.
+
+
+ h3 Searching
+
+ p Currently simple searching implemented - you can search by ObjectIds and by case-insensitive string matching. Examples:
+
+ pre
+ | /api/v1/person?name=joe
+ | /api/v1/position?person=4f99219333fa2efc68000006
+
+ p Better searching (exact matches, gte, etc) is on the TODO list.
+
+
View
4 instance-app/views/layout.jade
@@ -95,7 +95,9 @@ body#popit-united_kingdom(class=bodyClasses)
li
a(href='/FIXME') About PopIt
li
- a(href='/info/data-import') API / Data Freedom
+ a(href='/api') API
+ | /
+ a(href='/info/data-import') Data Freedom
li
a(href='/FIXME') Self Hosting
li
View
2  instance-app/views/organisation/view.jade
@@ -124,5 +124,5 @@ block content
section.raw-data
h2 Raw data
p The details for this organisation are also available in
- a(href="#{organisation.slug_url}/json") JSON
+ a(href="/api/v1/organisation/#{organisation.id}") JSON
| .
View
87 lib/apps/api.js → lib/apps/api/api_v1.js
@@ -52,13 +52,19 @@ current implementation state:
***/
+/*
+ FIXME: the url handling is not working elegantly. It is not possible to get
+ the correct url as we are an app in an app. Hardcoded atm, but needs
+ adrdessing. Might go away when Express 3.0 comes out.
+*/
+
var express = require('express'),
_ = require('underscore'),
regexp_quote = require('regexp-quote'),
- schemas = require('../schemas'),
- utils = require('../utils'),
- base_url = require('../middleware/route').base_url,
- current_absolute_url = require('../middleware/route').current_absolute_url;
+ schemas = require('../../schemas'),
+ utils = require('../../utils'),
+ base_url = require('../../middleware/route').base_url,
+ current_absolute_url = require('../../middleware/route').current_absolute_url;
var app = module.exports = express.createServer();
@@ -72,14 +78,40 @@ app.mounted( function (parent) {
})
);
+ app.use(
+ '/organisation',
+ create_api_endpoint({
+ schema_name: 'Organisation',
+ collection_fields: [ '_id', 'slug', 'name' ],
+ })
+ );
+
+ app.use(
+ '/position',
+ create_api_endpoint({
+ schema_name: 'Position',
+ collection_fields: [ '_id', 'title', 'person', 'organisation' ],
+ })
+ );
+
app.get('/', function (req, res, next) {
- res.json([]);
+
+ var api_base_url = base_url(req) + '/api/v1/';
+
+ res.json({
+ comment: "This is the API entry point - use a link in 'meta' to search a collection.",
+ meta: {
+ person_api_url: api_base_url + 'person',
+ organisation_api_url: api_base_url + 'organisation',
+ position_api_url: api_base_url + 'position',
+ },
+ });
});
- app.all('*', function(req, res, next) {
- // 404
- res.json({ error: "page not found"}, 404);
- });
+ // app.all('*', function(req, res, next) {
+ // // 404
+ // res.json({ error: "page not found"}, 404);
+ // });
});
@@ -107,7 +139,7 @@ function create_api_endpoint ( options ) {
// if the document_id is not the id redirect to it
if ( document_id != doc.id ) {
- res.redirect(req.app.set('basepath') + '/' + doc.id );
+ res.redirect(base_url(req) + '/api' + req.app.set('basepath') + '/' + doc.id );
} else {
res.local('doc', doc);
return next();
@@ -162,17 +194,25 @@ function create_api_endpoint ( options ) {
function read_collection (req,res,next) {
- var model = req.popit.model(options.schema_name);
- var where = {};
-
- var api_base_url = base_url(req) + req.app.set('basepath') + '/';
+ var model = req.popit.model(options.schema_name);
+ var schema = model.schema;
+ var where = {};
+
+ var api_base_url = base_url(req) + '/api' + req.app.set('basepath') + '/';
var edit_base_url = base_url(req);
// for each query parameter that we know about create a regex search.
- // TODO - default should not be regex but exact
_.each( req.query, function(value, key) {
- if (model.schema.path(key)) {
- where[key] = RegExp( regexp_quote(value), 'i' );
+ if ( schema.path(key) ) {
+
+ var key_type = schema.path(key).options.type;
+
+ if( key_type == String ) {
+ // TODO - default should not be regex but exact
+ where[key] = RegExp( regexp_quote(value), 'i' );
+ } else {
+ where[key] = value;
+ }
}
});
@@ -186,8 +226,11 @@ function create_api_endpoint ( options ) {
var result = doc.toObject();
result.meta = {
api_url: api_base_url + doc._id,
- edit_url: edit_base_url + doc.slug_url,
};
+
+ if (doc.slug_url)
+ result.meta.edit_url = edit_base_url + doc.slug_url;
+
return result;
});
@@ -218,9 +261,11 @@ function create_api_endpoint ( options ) {
}
});
- result.meta = {
- edit_url: base_url(req) + doc.slug_url,
- };
+ if (doc.slug_url) {
+ result.meta = {
+ edit_url: base_url(req) + doc.slug_url,
+ };
+ }
res.json( { result: result } );
}
View
16 lib/apps/api/index.js
@@ -0,0 +1,16 @@
+var express = require('express');
+
+var app
+ = module.exports
+ = express.createServer();
+
+app.use( '/v1', require('./api_v1') );
+
+app.get('/', function (req, res) {
+ res.render('api/index');
+});
+
+app.all('*', function(req, res, next) {
+ // 404
+ res.json({ error: "page not found"}, 404);
+});
View
75 tests/api/basics.js
@@ -29,6 +29,20 @@ module.exports = {
test_server_helpers.stop_servers(tearDown_done);
},
+ "access API docs": function (test) {
+
+ test.expect(2);
+
+ this.rest.get('/api').on('complete', function(data, response) {
+
+ // check for 200 and json
+ test.equal(response.statusCode, 200, "got 200 response");
+ test.equal(response.headers['content-type'], 'text/html; charset=utf-8', "got HTML");
+
+ test.done();
+ });
+ },
+
"access API": function (test) {
test.expect(3);
@@ -40,7 +54,18 @@ module.exports = {
test.equal(response.headers['content-type'], 'application/json; charset=utf-8', "got JSON");
// check that data looks right
- test.deepEqual(data, [], "data is empty array");
+ test.deepEqual(
+ data,
+ {
+ comment: 'This is the API entry point - use a link in \'meta\' to search a collection.',
+ meta: {
+ person_api_url: 'http://foobar.vcap.me:3101/api/v1/person',
+ organisation_api_url: 'http://foobar.vcap.me:3101/api/v1/organisation',
+ position_api_url: 'http://foobar.vcap.me:3101/api/v1/position',
+ },
+ },
+ "response in links to parts of the API"
+ );
test.done();
});
@@ -207,6 +232,54 @@ module.exports = {
});
},
+ "list all positions": function (test) {
+ test.expect(2);
+
+ this.rest
+ .get('position')
+ .on('complete', function(data, response) {
+ test.equal(response.statusCode, 200, "got 200 response");
+
+ // test we got the first four presidents
+ test.deepEqual(
+ _.pluck(data.results, 'title').sort(),
+ [ 'President', 'President', 'President', 'President' ].sort(),
+ "got expected titles"
+ );
+
+ test.done();
+ });
+
+ },
+
+ "list one persons positions": function (test) {
+ test.expect(2);
+
+ this.rest
+ .get('position?person=4f9ea1326e8770d854c45a1e') // Clinton
+ .on('complete', function(data, response) {
+ test.equal(response.statusCode, 200, "got 200 response");
+
+ // test we got the first four presidents
+ test.deepEqual(
+ data.results,
+ [{
+ _id: '4f9ea1326e8770d854c45a23',
+ title: 'President',
+ person: '4f9ea1326e8770d854c45a1e',
+ organisation: '4f9ea1326e8770d854c45a21',
+ meta: {
+ api_url: 'http://foobar.vcap.me:3101/api/v1/position/4f9ea1326e8770d854c45a23',
+ },
+ }],
+ "got Clinton's Presidency"
+ );
+
+ test.done();
+ });
+
+ },
+
// test pagination
// test sorting

No commit comments for this range

Something went wrong with that request. Please try again.