Commit a2b984ad authored by Aurélien Vermylen's avatar Aurélien Vermylen

First tested and working version of Automatic API Storage.

parent 76ddf154
...@@ -182,6 +182,7 @@ module.exports = function (grunt) { ...@@ -182,6 +182,7 @@ module.exports = function (grunt) {
'src/jio.storage/cryptstorage.js', 'src/jio.storage/cryptstorage.js',
'src/jio.storage/websqlstorage.js', 'src/jio.storage/websqlstorage.js',
'src/jio.storage/mappingstorage.js', 'src/jio.storage/mappingstorage.js',
'src/jio.storage/automaticapistorage.js',
'src/jio.storage/fbstorage.js' 'src/jio.storage/fbstorage.js'
], ],
dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js'
......
...@@ -13796,7 +13796,1085 @@ return new Parser; ...@@ -13796,7 +13796,1085 @@ return new Parser;
jIO.addStorage('websql', WebSQLStorage); jIO.addStorage('websql', WebSQLStorage);
}(jIO, RSVP, Blob, openDatabase)); }(jIO, RSVP, Blob, openDatabase));
;/*jslint nomen: true */ ;/*jslint indent:2, maxlen: 80, nomen: true */
/*global jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory,
Query, FormData*/
(function (jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory,
Query, FormData) {
"use strict";
function getSubIdEqualSubProperty(storage, value, key) {
var query;
if (storage._no_sub_query_id) {
throw new jIO.util.jIOError('no sub query id active', 404);
}
if (!value) {
throw new jIO.util.jIOError(
'can not find document with ' + key + ' : undefined',
404
);
}
if (storage._mapping_id_memory_dict[value]) {
return storage._mapping_id_memory_dict[value];
}
query = new SimpleQuery({
key: key,
value: value,
type: "simple"
});
if (storage._query.query !== undefined) {
query = new ComplexQuery({
operator: "AND",
query_list: [query, storage._query.query],
type: "complex"
});
}
query = Query.objectToSearchText(query);
return storage._sub_storage.allDocs({
"query": query,
"sort_on": storage._query.sort_on,
"select_list": storage._query.select_list,
"limit": storage._query.limit
})
.push(function (data) {
if (data.data.total_rows === 0) {
throw new jIO.util.jIOError(
"Can not find document with (" + key + ", " + value + ")",
404
);
}
if (data.data.total_rows > 1) {
throw new TypeError("id must be unique field: " + key
+ ", result:" + data.data.rows.toString());
}
storage._mapping_id_memory_dict[value] = data.data.rows[0].id;
return data.data.rows[0].id;
});
}
/*jslint unparam: true*/
var mapping_function = {
"equalSubProperty": {
"mapToSubProperty": function (property, sub_doc, doc, args, id) {
sub_doc[args] = doc[property];
return args;
},
"mapToMainProperty": function (property, sub_doc, doc, args, sub_id) {
if (sub_doc.hasOwnProperty(args)) {
doc[property] = sub_doc[args];
}
return args;
},
"mapToSubId": function (storage, doc, id, args) {
if (doc !== undefined) {
if (storage._property_for_sub_id &&
doc.hasOwnProperty(storage._property_for_sub_id)) {
return doc[storage._property_for_sub_id];
}
}
return getSubIdEqualSubProperty(storage, id, storage._map_id[1]);
},
"mapToId": function (storage, sub_doc, sub_id, args) {
return sub_doc[args];
}
},
"equalValue": {
"mapToSubProperty": function (property, sub_doc, doc, args) {
sub_doc[property] = args;
return property;
},
"mapToMainProperty": function (property) {
return property;
}
},
"ignore": {
"mapToSubProperty": function () {
return false;
},
"mapToMainProperty": function (property) {
return property;
}
},
"equalSubId": {
"mapToSubProperty": function (property, sub_doc, doc) {
sub_doc[property] = doc[property];
return property;
},
"mapToMainProperty": function (property, sub_doc, doc, args, sub_id) {
if (sub_id === undefined && sub_doc.hasOwnProperty(property)) {
doc[property] = sub_doc[property];
} else {
doc[property] = sub_id;
}
return property;
},
"mapToSubId": function (storage, doc, id, args) {
return id;
},
"mapToId": function (storage, sub_doc, sub_id) {
return sub_id;
}
},
"keep": {
"mapToSubProperty": function (property, sub_doc, doc) {
sub_doc[property] = doc[property];
return property;
},
"mapToMainProperty": function (property, sub_doc, doc) {
doc[property] = sub_doc[property];
return property;
}
},
"switchPropertyValue": {
"mapToSubProperty": function (property, sub_doc, doc, args) {
sub_doc[args[0]] = args[1][doc[property]];
return args[0];
},
"mapToMainProperty": function (property, sub_doc, doc, args) {
var subvalue, value = sub_doc[args[0]];
for (subvalue in args[1]) {
if (args[1].hasOwnProperty(subvalue)) {
if (value === args[1][subvalue]) {
doc[property] = subvalue;
return property;
}
}
}
}
}
};
/*jslint unparam: false*/
function initializeQueryAndDefaultMapping(storage) {
var property, query_list = [];
for (property in storage._mapping_dict) {
if (storage._mapping_dict.hasOwnProperty(property)) {
if (storage._mapping_dict[property][0] === "equalValue") {
if (storage._mapping_dict[property][1] === undefined) {
throw new jIO.util.jIOError("equalValue has not parameter", 400);
}
storage._default_mapping[property] =
storage._mapping_dict[property][1];
query_list.push(new SimpleQuery({
key: property,
value: storage._mapping_dict[property][1],
type: "simple"
}));
}
if (storage._mapping_dict[property][0] === "equalSubId") {
if (storage._property_for_sub_id !== undefined) {
throw new jIO.util.jIOError(
"equalSubId can be defined one time",
400
);
}
storage._property_for_sub_id = property;
}
}
}
if (storage._map_id[0] === "equalSubProperty") {
storage._mapping_dict[storage._map_id[1]] = ["keep"];
}
if (storage._query.query !== undefined) {
query_list.push(QueryFactory.create(storage._query.query));
}
if (query_list.length > 1) {
storage._query.query = new ComplexQuery({
type: "complex",
query_list: query_list,
operator: "AND"
});
} else if (query_list.length === 1) {
storage._query.query = query_list[0];
}
}
function MappingStorage(spec) {
this._mapping_dict = spec.property || {};
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._map_all_property = spec.map_all_property !== undefined ?
spec.map_all_property : true;
this._no_sub_query_id = spec.no_sub_query_id;
this._attachment_mapping_dict = spec.attachment || {};
this._query = spec.query || {};
this._map_id = spec.id || ["equalSubId"];
this._id_mapped = (spec.id !== undefined) ? spec.id[1] : false;
if (this._query.query !== undefined) {
this._query.query = QueryFactory.create(this._query.query);
}
this._default_mapping = {};
this._mapping_id_memory_dict = {};
this._attachment_list = spec.attachment_list || [];
initializeQueryAndDefaultMapping(this);
}
function getAttachmentId(storage, sub_id, attachment_id, method) {
var mapping_dict = storage._attachment_mapping_dict;
if (mapping_dict !== undefined
&& mapping_dict[attachment_id] !== undefined
&& mapping_dict[attachment_id][method] !== undefined
&& mapping_dict[attachment_id][method].uri_template !== undefined) {
return UriTemplate.parse(
mapping_dict[attachment_id][method].uri_template
).expand({id: sub_id});
}
return attachment_id;
}
function getSubStorageId(storage, id, doc) {
return new RSVP.Queue()
.push(function () {
var map_info = storage._map_id || ["equalSubId"];
if (storage._property_for_sub_id && doc !== undefined &&
doc.hasOwnProperty(storage._property_for_sub_id)) {
return doc[storage._property_for_sub_id];
}
return mapping_function[map_info[0]].mapToSubId(
storage,
doc,
id,
map_info[1]
);
});
}
function mapToSubProperty(storage, property, sub_doc, doc, id) {
var mapping_info = storage._mapping_dict[property] || ["keep"];
return mapping_function[mapping_info[0]].mapToSubProperty(
property,
sub_doc,
doc,
mapping_info[1],
id
);
}
function mapToMainProperty(storage, property, sub_doc, doc, sub_id) {
var mapping_info = storage._mapping_dict[property] || ["keep"];
return mapping_function[mapping_info[0]].mapToMainProperty(
property,
sub_doc,
doc,
mapping_info[1],
sub_id
);
}
function mapToMainDocument(storage, sub_doc, sub_id) {
var doc = {},
property,
property_list = [storage._id_mapped];
for (property in storage._mapping_dict) {
if (storage._mapping_dict.hasOwnProperty(property)) {
property_list.push(mapToMainProperty(
storage,
property,
sub_doc,
doc,
sub_id
));
}
}
if (storage._map_all_property) {
for (property in sub_doc) {
if (sub_doc.hasOwnProperty(property)) {
if (property_list.indexOf(property) < 0) {
doc[property] = sub_doc[property];
}
}
}
}
if (storage._map_for_sub_storage_id !== undefined) {
doc[storage._map_for_sub_storage_id] = sub_id;
}
return doc;
}
function mapToSubstorageDocument(storage, doc, id) {
var sub_doc = {}, property;
for (property in doc) {
if (doc.hasOwnProperty(property)) {
mapToSubProperty(storage, property, sub_doc, doc, id);
}
}
for (property in storage._default_mapping) {
if (storage._default_mapping.hasOwnProperty(property)) {
sub_doc[property] = storage._default_mapping[property];
}
}
if (storage._map_id[0] === "equalSubProperty" && id !== undefined) {
sub_doc[storage._map_id[1]] = id;
}
return sub_doc;
}
function handleAttachment(storage, argument_list, method) {
return getSubStorageId(storage, argument_list[0])
.push(function (sub_id) {
argument_list[0] = sub_id;
var old_id = argument_list[1];
argument_list[1] = getAttachmentId(
storage,
argument_list[0],
argument_list[1],
method
);
if (storage._attachment_list.length > 0
&& storage._attachment_list.indexOf(old_id) < 0) {
if (method === "get") {
throw new jIO.util.jIOError("unhautorized attachment", 404);
}
return;
}
return storage._sub_storage[method + "Attachment"].apply(
storage._sub_storage,
argument_list
);
});
}
MappingStorage.prototype.get = function (id) {
var storage = this;
return getSubStorageId(this, id)
.push(function (sub_id) {
return storage._sub_storage.get(sub_id)
.push(function (sub_doc) {
return mapToMainDocument(storage, sub_doc, sub_id);
});
});
};
MappingStorage.prototype.post = function (doc) {
var sub_doc = mapToSubstorageDocument(
this,
doc
),
id = doc[this._property_for_sub_id],
storage = this;
if (this._property_for_sub_id && id !== undefined) {
return this._sub_storage.put(id, sub_doc);
}
if (this._id_mapped && doc[this._id_mapped] !== undefined) {
return getSubStorageId(storage, id, doc)
.push(function (sub_id) {
return storage._sub_storage.put(sub_id, sub_doc);
})
.push(function () {
return doc[storage._id_mapped];
})
.push(undefined, function (error) {
if (error instanceof jIO.util.jIOError) {
return storage._sub_storage.post(sub_doc);
}
throw error;
});
}
throw new jIO.util.jIOError(
"post is not supported with id mapped",
400
);
};
MappingStorage.prototype.put = function (id, doc) {
var storage = this,
sub_doc = mapToSubstorageDocument(this, doc, id);
return getSubStorageId(this, id, doc)
.push(function (sub_id) {
return storage._sub_storage.put(sub_id, sub_doc);
})
.push(undefined, function (error) {
if (error instanceof jIO.util.jIOError && error.status_code === 404) {
return storage._sub_storage.post(sub_doc);
}
throw error;
})
.push(function () {
return id;
});
};
MappingStorage.prototype.remove = function (id) {
var storage = this;
return getSubStorageId(this, id)
.push(function (sub_id) {
return storage._sub_storage.remove(sub_id);
})
.push(function () {
return id;
});
};
MappingStorage.prototype.getAttachment = function () {
return handleAttachment(this, arguments, "get");
};
MappingStorage.prototype.putAttachment = function (id, attachment_id, blob) {
var storage = this,
mapping_dict = storage._attachment_mapping_dict;
// THIS IS REALLY BAD, FIND AN OTHER WAY IN FUTURE
if (mapping_dict !== undefined
&& mapping_dict[attachment_id] !== undefined
&& mapping_dict[attachment_id].put !== undefined
&& mapping_dict[attachment_id].put.erp5_put_template !== undefined) {
return getSubStorageId(storage, id)
.push(function (sub_id) {
var url = UriTemplate.parse(
mapping_dict[attachment_id].put.erp5_put_template
).expand({id: sub_id}),
data = new FormData();
data.append("field_my_file", blob);
data.append("form_id", "File_view");
return jIO.util.ajax({
"type": "POST",
"url": url,
"data": data,
"xhrFields": {
withCredentials: true
}
});
});
}
return handleAttachment(this, arguments, "put", id)
.push(function () {
return attachment_id;
});
};
MappingStorage.prototype.removeAttachment = function (id, attachment_id) {
return handleAttachment(this, arguments, "remove", id)
.push(function () {
return attachment_id;
});
};
MappingStorage.prototype.allAttachments = function (id) {
var storage = this, sub_id;
return getSubStorageId(storage, id)
.push(function (sub_id_result) {
sub_id = sub_id_result;
return storage._sub_storage.allAttachments(sub_id);
})
.push(function (result) {
var attachment_id,
attachments = {},
mapping_dict = {},
i;
for (attachment_id in storage._attachment_mapping_dict) {
if (storage._attachment_mapping_dict.hasOwnProperty(attachment_id)) {
mapping_dict[getAttachmentId(storage, sub_id, attachment_id, "get")]
= attachment_id;
}
}
for (attachment_id in result) {
if (result.hasOwnProperty(attachment_id)) {
if (!(storage._attachment_list.length > 0
&& storage._attachment_list.indexOf(attachment_id) < 0)) {
if (mapping_dict.hasOwnProperty(attachment_id)) {
attachments[mapping_dict[attachment_id]] = {};
} else {
attachments[attachment_id] = {};
}
}
}
}
for (i = 0; i < storage._attachment_list.length; i += 1) {
if (!attachments.hasOwnProperty(storage._attachment_list[i])) {
attachments[storage._attachment_list[i]] = {};
}
}
return attachments;
});
};
MappingStorage.prototype.hasCapacity = function (name) {
return this._sub_storage.hasCapacity(name);
};
MappingStorage.prototype.repair = function () {
return this._sub_storage.repair.apply(this._sub_storage, arguments);
};
MappingStorage.prototype.bulk = function (id_list) {
var storage = this;
function mapId(parameter) {
return getSubStorageId(storage, parameter.parameter_list[0])
.push(function (id) {
return {"method": parameter.method, "parameter_list": [id]};
});
}
return new RSVP.Queue()
.push(function () {
var promise_list = id_list.map(mapId);
return RSVP.all(promise_list);
})
.push(function (id_list_mapped) {
return storage._sub_storage.bulk(id_list_mapped);
})
.push(function (result) {
var mapped_result = [], i;
for (i = 0; i < result.length; i += 1) {
mapped_result.push(mapToMainDocument(
storage,
result[i]
));
}
return mapped_result;
});
};
MappingStorage.prototype.buildQuery = function (option) {
var storage = this,
i,
query,
property,
select_list = [],
sort_on = [];
function mapQuery(one_query) {
var j, query_list = [], key, sub_query;
if (one_query.type === "complex") {
for (j = 0; j < one_query.query_list.length; j += 1) {
sub_query = mapQuery(one_query.query_list[j]);
if (sub_query) {
query_list.push(sub_query);
}
}
one_query.query_list = query_list;
return one_query;
}
key = mapToMainProperty(storage, one_query.key, {}, {});
if (key !== undefined) {
one_query.key = key;
return one_query;
}
return false;
}
if (option.sort_on !== undefined) {
for (i = 0; i < option.sort_on.length; i += 1) {
property = mapToMainProperty(this, option.sort_on[i][0], {}, {});
if (property && sort_on.indexOf(property) < 0) {
sort_on.push([property, option.sort_on[i][1]]);
}
}
}
if (this._query.sort_on !== undefined) {
for (i = 0; i < this._query.sort_on.length; i += 1) {
property = mapToMainProperty(this, this._query.sort_on[i], {}, {});
if (sort_on.indexOf(property) < 0) {
sort_on.push([property, option.sort_on[i][1]]);
}
}
}
if (option.select_list !== undefined) {
for (i = 0; i < option.select_list.length; i += 1) {
property = mapToMainProperty(this, option.select_list[i], {}, {});
if (property && select_list.indexOf(property) < 0) {
select_list.push(property);
}
}
}
if (this._query.select_list !== undefined) {
for (i = 0; i < this._query.select_list; i += 1) {
property = this._query.select_list[i];
if (select_list.indexOf(property) < 0) {
select_list.push(property);
}
}
}
if (this._id_mapped) {
// modify here for future way to map id
select_list.push(this._id_mapped);
}
if (option.query !== undefined) {
query = mapQuery(QueryFactory.create(option.query));
}
if (this._query.query !== undefined) {
if (query === undefined) {
query = this._query.query;
}
query = new ComplexQuery({
operator: "AND",
query_list: [query, this._query.query],
type: "complex"
});
}
if (query !== undefined) {
query = Query.objectToSearchText(query);
}
return this._sub_storage.allDocs(
{
query: query,
select_list: select_list,
sort_on: sort_on,
limit: option.limit
}
)
.push(function (result) {
var sub_doc, map_info = storage._map_id || ["equalSubId"];
for (i = 0; i < result.data.total_rows; i += 1) {
sub_doc = result.data.rows[i].value;
result.data.rows[i].id =
mapping_function[map_info[0]].mapToId(
storage,
sub_doc,
result.data.rows[i].id,
map_info[1]
);
result.data.rows[i].value =
mapToMainDocument(
storage,
sub_doc
);
}
return result.data.rows;
});
};
jIO.addStorage('mapping', MappingStorage);
}(jIO, RSVP, UriTemplate, SimpleQuery, ComplexQuery, QueryFactory, Query,
FormData));;/*
* Copyright 2017, ClearRoad Inc.
*
* Authors: Aurélien Vermylen
*/
/**
* JIO Automatic API Storage. Type = "automaticapi".
* Automatic "handler" storage.
*/
/*global URI, Blob, jIO, RSVP, UriTemplate*/
/*jslint nomen: true*/
(function (jIO, RSVP, UriTemplate, URI, Blob) {
'use strict';
// Some Automatic API related constants.
var AUTOMATIC_BASE_PROTOCOL = 'https',
AUTOMATIC_BASE_DOMAIN = 'api.automatic.com',
AUTOMATIC_BASE_URI = AUTOMATIC_BASE_PROTOCOL + '://'
+ AUTOMATIC_BASE_DOMAIN,
/*AUTOMATIC_VALID_ENDPOINTS = ['/trip/', '/trip/{id}/', '/user/',
'/user/{id}/', '/user/{user_id}/device/',
'/user/{user_id}/device/{device_id}/', '/vehicle/{id}/', '/vehicle/',
'/vehicle/{vehicle_id}/mil/'],*/
AUTOMATIC_VALID_ENDPOINT_REGEXES = [/^\/trip(\/T_\w*|)\/$/,
/^\/user\/me(\/device(\/\w*|)|)\/$/,
/^\/vehicle(\/C_\w*(\/mil|)|)\/$/],
AUTOMATIC_VALID_ENDPOINTID_REGEXES = [/^\/trip\/T_\w*\/$/,
/^\/user\/me\/device\/\w*\/$/,
/^\/vehicle\/C_\w*\/$/],
automatic_template = UriTemplate.parse(AUTOMATIC_BASE_URI +
'{/endpoint*}');
// Check validity of endpoint.
function checkEndpoint(endpoint) {
var i;
for (i = 0; i < AUTOMATIC_VALID_ENDPOINT_REGEXES.length; i += 1) {
if (AUTOMATIC_VALID_ENDPOINT_REGEXES[i].test(endpoint)) {
return true;
}
}
return false;
}
// Check that ids match a valid Automatic API id
function checkEndpointAsId(endpoint) {
var i;
for (i = 0; i < AUTOMATIC_VALID_ENDPOINTID_REGEXES.length; i += 1) {
if (AUTOMATIC_VALID_ENDPOINTID_REGEXES[i].test(endpoint)) {
return true;
}
}
return false;
}
function _queryAutomaticAPI(endpoint, filters, jio) {
var result = [],
type = endpoint.split('/')[1],
promises,
usr = filters.user || 'all',
isId = checkEndpointAsId(endpoint),
user_dict = {},
i;
// Remove 'user' filter which should not be put in Automatic request.
delete filters.user;
// Check endpoint validity.
if (!checkEndpoint(endpoint)) {
throw new jIO.util.jIOError('Wrong Automatic API query. (usually ' +
'caused by wrong "id" or "type")', 400);
}
// Promise chain to handle multi-part response ("_metadata"->"next" parts).
function treatNext(returned) {
var data,
user_id,
next;
// If the returned value was an error, return it (-> quite nasty design
// to have to return errors, but this is in order for all RSVP.all() to
// continue till the end.
if (returned instanceof jIO.util.jIOError) {
return returned;
}
data = returned[0];
user_id = user_dict[returned[1]];
data = [data];
if (!isId) {
if (data[0]._metadata === undefined) {
return new jIO.util.jIOError('Malformed Automatic API result.', 500);
}
next = data[0]._metadata.next;
data = data[0].results;
}
return RSVP.Queue().push(function () {
return RSVP.all(data.map(function (dat) {
var path, temp;
path = URI(dat.url).path();
temp = {
'path': path,
'reference': '/' + user_id + path,
'id': '/' + user_id + path,
'type': type,
'started_at': dat.started_at || null,
'ended_at': dat.ended_at || null,
'user': user_id
};
result.push(temp);
return jio._cache.put('/' + user_id + path, temp).push(function () {
return jio._cache.putAttachment('/' + user_id + path, 'data',
new Blob([JSON.stringify(dat)], {type:
'text/plain'}));
});
}));
}).push(function () {
if (next === undefined || next === null) {
return true;
}
return RSVP.Queue().push(function () {
return jIO.util.ajax({
'type': 'GET',
'url': next,
'headers': {'Authorization': 'Bearer ' + returned[1]}
//'xhrFields': {withCredentials: true}
});
}).push(function (response) {
return [JSON.parse(response.target.responseText), returned[1]];
}, function (response) {
return new jIO.util.jIOError(
response.target.responseText,
response.target.status
);
}).push(treatNext);
}, function (error) {
return error;
});
}
// Start of the promise chain per token.
promises = jio._access_tokens.map(function (token) {
return new RSVP.Queue().push(function () {
return jIO.util.ajax({
'type': 'GET',
'url': automatic_template.expand({endpoint: ['user', 'me', '']}),
'headers': {'Authorization': 'Bearer ' + token}
//'xhrFields': {withCredentials: true}
});
}).push(function (respusr) {
user_dict[token] = JSON.parse(respusr.target.responseText).id;
if (usr === 'all' || usr === user_dict[token]) {
return jIO.util.ajax({
'type': 'GET',
'url': URI(automatic_template.expand({endpoint:
endpoint.split('/').splice(1)})).search(filters).toString(),
'headers': {'Authorization': 'Bearer ' + token}
//'xhrFields': {withCredentials: true}
});
}
return new jIO.util.jIOError('Ignored.', 200);
}).push(function (response) {
if (response instanceof jIO.util.jIOError) {
return response;
}
return [JSON.parse(response.target.responseText), token];
}, function (response) {
return new jIO.util.jIOError(
response.target.responseText,
response.target.status
);
}).push(treatNext);
});
return new RSVP.Queue().push(function () {
return RSVP.all(promises);
}).push(function (trueOrErrorArray) {
// If we queried an id, return results should be length 1
if (isId && (usr !== 'all')) {
if (result.length === 1) {
return result[0];
}
if (result.length > 1) {
throw new jIO.util.jIOError('Automatic API returned more' +
' than one result for id endpoint', 500);
}
// Result is empty, so we throw the correct token error.
i = jio._access_tokens.map(function (token) {
if (user_dict[token] === usr) {
return true;
}
return false;
}).indexOf(true);
if (i < trueOrErrorArray.length && i > -1 &&
trueOrErrorArray[i] instanceof jIO.util.jIOError) {
throw trueOrErrorArray[i];
}
// If we didn't find the error in the promise returns, we don't have
// a token for user usr.
throw new jIO.util.jIOError('No valid token for user: ' + usr, 400);
}
// Otherwise return results and errors and let caller handle.
return result;
});
}
/**
* The Automatic API Storage extension
*
* @class AutomaticAPIStorage
* @constructor
*/
function AutomaticAPIStorage(spec) {
var i;
if (typeof spec.access_tokens !== 'object' || !spec.access_tokens) {
throw new TypeError('Access Tokens must be a non-empty array.');
}
for (i = 0; i < spec.access_tokens.length; i += 1) {
if (typeof spec.access_tokens[i] !== 'string' || !spec.access_tokens[i]) {
throw new jIO.util.jIOError('Access Tokens must be' +
' an array of non-empty strings.', 400);
}
}
this._access_tokens = spec.access_tokens;
this._cache = jIO.createJIO({type: 'memory'});
}
AutomaticAPIStorage.prototype.get = function (id) {
var self = this,
endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400);
}
if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400);
}
return this._cache.get(id).push(function (res) {
return res;
}, function () {
return _queryAutomaticAPI(endpoint, {'user': usr}, self)
.push(function (res) {
return res;
}, function (err) {
throw new jIO.util.jIOError('Cannot find document: ' + id +
', Error: ' + err.message, 404);
});
});
};
AutomaticAPIStorage.prototype.put = function () {
return;
};
AutomaticAPIStorage.prototype.post = function () {
return;
};
AutomaticAPIStorage.prototype.remove = function () {
return;
};
AutomaticAPIStorage.prototype.getAttachment = function (id, name, options) {
var self = this,
endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400);
}
if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400);
}
return this._cache.get(id).push(function () {
return self._cache.getAttachment(id, name, options);
}, function () {
return self.get(id)
.push(function () {
return self._cache.getAttachment(id, name, options);
});
});
};
AutomaticAPIStorage.prototype.putAttachment = function () {
return;
};
AutomaticAPIStorage.prototype.removeAttachment = function () {
return;
};
AutomaticAPIStorage.prototype.allAttachments = function (id) {
var endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400);
}
if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400);
}
return {data: null};
};
AutomaticAPIStorage.prototype.repair = function () {
return;
};
AutomaticAPIStorage.prototype.hasCapacity = function (name) {
return ((name === 'list') || (name === 'query'));
};
AutomaticAPIStorage.prototype.buildQuery = function (options) {
var parsed_query = jIO.QueryFactory.create(options.query),
key_list,
automatic_filters = {},
simplequery_type_value,
intercept_keys = ['started_at', 'ended_at', 'user', 'vehicle'],
intercept_accepted_operators = [['>', '>=', '<', '<='],
['>', '>=', '<', '<='], ['='], ['=']],
temp_operator_index,
temp_key,
automatic_endpoint,
i,
j;
// remove query from the options
delete options.query;
// XXX: check if there is no built-in method to seek in queries for
// specific keys...
function extractKeysFromQuery(quer) {
var keys_list = [],
i;
if (quer.type === 'complex') {
for (i = 0; i < quer.query_list.length; i += 1) {
keys_list = keys_list.concat(
extractKeysFromQuery(quer.query_list[i])
);
}
} else if (quer.type === 'simple') {
keys_list.push(quer.key);
}
return keys_list;
}
function findSimpleQueryForKey(key, quer) {
var temp,
result = [],
i;
if (quer.type === 'complex') {
for (i = 0; i < quer.query_list.length; i += 1) {
temp = findSimpleQueryForKey(key, quer.query_list[i]);
if (temp !== undefined) {
result = result.concat(temp);
}
}
} else if (quer.type === 'simple' && quer.key === key) {
result.push([quer.operator, quer.value]);
}
return result;
}
key_list = extractKeysFromQuery(parsed_query);
// main loop that forms the filters to pass on to the HTTP call.
for (i = 0; i < intercept_keys.length; i += 1) {
if (key_list.indexOf(intercept_keys[i]) > -1) {
simplequery_type_value = findSimpleQueryForKey(intercept_keys[i],
parsed_query);
for (j = 0; j < simplequery_type_value.length; j += 1) {
temp_operator_index = intercept_accepted_operators[i].indexOf(
simplequery_type_value[j][0]
);
if (temp_operator_index > -1) {
if (i < 2) {
temp_key = intercept_keys[i] +
(temp_operator_index < 2 ? '__gte' : '__lte');
automatic_filters[temp_key] = (new Date(
simplequery_type_value[j][1]
).getTime() / 1000).toString();
} else {
temp_key = intercept_keys[i];
automatic_filters[temp_key] = simplequery_type_value[j][1];
}
}
}
}
}
// Remove the 'type = value' query part: XXX -> add id retrieval!
simplequery_type_value = findSimpleQueryForKey('type', parsed_query);
if (simplequery_type_value.length === 0) {
throw new jIO.util.jIOError('AutomaticAPIStorage Query must' +
' always contain "type".', 400);
}
if (simplequery_type_value.length > 1) {
throw new jIO.util.jIOError('AutomaticAPIStorage Query must' +
' contain "type" constraint only once.', 400);
}
if (simplequery_type_value[0][0] !== '=') {
throw new jIO.util.jIOError('AutomaticAPIStorage Query must' +
' contain "type" constraint with "=" operator.', 400);
}
automatic_endpoint = '/' + simplequery_type_value[0][1] + '/';
return _queryAutomaticAPI(automatic_endpoint, automatic_filters, this)
.push(function (results) {
if (!(results instanceof Array)) {
results = [results];
}
return parsed_query.exec(results, options);
});
};
jIO.addStorage('automaticapi', AutomaticAPIStorage);
}(jIO, RSVP, UriTemplate, URI, Blob));;/*jslint nomen: true */
/*global RSVP, UriTemplate*/ /*global RSVP, UriTemplate*/
(function (jIO, RSVP, UriTemplate) { (function (jIO, RSVP, UriTemplate) {
"use strict"; "use strict";
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
* JIO Automatic API Storage. Type = "automaticapi". * JIO Automatic API Storage. Type = "automaticapi".
* Automatic "handler" storage. * Automatic "handler" storage.
*/ */
/*global Blob, jIO, RSVP, UriTemplate*/ /*global URI, Blob, jIO, RSVP, UriTemplate*/
/*jslint nomen: true*/ /*jslint nomen: true*/
(function (jIO, RSVP, UriTemplate, URI) { (function (jIO, RSVP, UriTemplate, URI, Blob) {
'use strict'; 'use strict';
// Some Automatic API related constants. // Some Automatic API related constants.
var AUTOMATIC_BASE_PROTOCOL = 'https', var AUTOMATIC_BASE_PROTOCOL = 'https',
...@@ -21,20 +21,20 @@ ...@@ -21,20 +21,20 @@
'/user/{id}/', '/user/{user_id}/device/', '/user/{id}/', '/user/{user_id}/device/',
'/user/{user_id}/device/{device_id}/', '/vehicle/{id}/', '/vehicle/', '/user/{user_id}/device/{device_id}/', '/vehicle/{id}/', '/vehicle/',
'/vehicle/{vehicle_id}/mil/'],*/ '/vehicle/{vehicle_id}/mil/'],*/
AUTOMATIC_VALID_ENDPOINT_REGEXES = [/^\/(U_\w*|all)\/trip(\/T_\w*|)\/$/, AUTOMATIC_VALID_ENDPOINT_REGEXES = [/^\/trip(\/T_\w*|)\/$/,
/^\/(U_\w*|all)\/user\/me(\/device(\/\w*|)|)\/$/, /^\/user\/me(\/device(\/\w*|)|)\/$/,
/^\/(U_\w*|all)\/vehicle(\/C_\w*(\/mil|)|)\/$/], /^\/vehicle(\/C_\w*(\/mil|)|)\/$/],
AUTOMATIC_VALID_ENDPOINTID_REGEXES = [/^\/(U_\w*|all)\/trip\/T_\w*\/$/, AUTOMATIC_VALID_ENDPOINTID_REGEXES = [/^\/trip\/T_\w*\/$/,
/^\/(U_\w*|all)\/user\/me\/device\/\w*\/$/, /^\/user\/me\/device\/\w*\/$/,
/^\/(U_\w*|all)\/vehicle\/C_\w*\/$/], /^\/vehicle\/C_\w*\/$/],
automatic_template = UriTemplate.parse(AUTOMATIC_BASE_URI + '{endpoint}'); automatic_template = UriTemplate.parse(AUTOMATIC_BASE_URI +
'{/endpoint*}');
// Check validity of endpoint. // Check validity of endpoint.
function checkEndpoint(endpoint) { function checkEndpoint(endpoint) {
var regex; var i;
for (regex in AUTOMATIC_VALID_ENDPOINT_REGEXES) { for (i = 0; i < AUTOMATIC_VALID_ENDPOINT_REGEXES.length; i += 1) {
if (AUTOMATIC_VALID_ENDPOINT_REGEXES.hasOwnProperty(regex) if (AUTOMATIC_VALID_ENDPOINT_REGEXES[i].test(endpoint)) {
&& regex.test(endpoint)) {
return true; return true;
} }
} }
...@@ -43,10 +43,9 @@ ...@@ -43,10 +43,9 @@
// Check that ids match a valid Automatic API id // Check that ids match a valid Automatic API id
function checkEndpointAsId(endpoint) { function checkEndpointAsId(endpoint) {
var regex; var i;
for (regex in AUTOMATIC_VALID_ENDPOINTID_REGEXES) { for (i = 0; i < AUTOMATIC_VALID_ENDPOINTID_REGEXES.length; i += 1) {
if (AUTOMATIC_VALID_ENDPOINTID_REGEXES.hasOwnProperty(regex) if (AUTOMATIC_VALID_ENDPOINTID_REGEXES[i].test(endpoint)) {
&& regex.test(endpoint)) {
return true; return true;
} }
} }
...@@ -55,96 +54,144 @@ ...@@ -55,96 +54,144 @@
function _queryAutomaticAPI(endpoint, filters, jio) { function _queryAutomaticAPI(endpoint, filters, jio) {
var result = [], var result = [],
type, type = endpoint.split('/')[1],
promises, promises,
usr = filters.user || 'all',
isId = checkEndpointAsId(endpoint),
user_dict = {}, user_dict = {},
temp,
i; i;
// Remove 'user' filter which should not be put in Automatic request.
delete filters.user;
// Check endpoint validity.
if (!checkEndpoint(endpoint)) { if (!checkEndpoint(endpoint)) {
throw new jIO.util.jIOError('Wrong Automatic API query. (usually ' + throw new jIO.util.jIOError('Wrong Automatic API query. (usually ' +
'caused by wrong "id" or "type")', 400); 'caused by wrong "id" or "type")', 400);
} }
type = endpoint.split('/')[2];
// Promise chain to handle multi-part response ("_metadata"->"next" parts). // Promise chain to handle multi-part response ("_metadata"->"next" parts).
function treatNext(returned) { function treatNext(returned) {
var path, var data,
data,
user_id, user_id,
next; next;
// If the returned value was an error, return it (-> quite nasty design
// to have to return errors, but this is in order for all RSVP.all() to
// continue till the end.
if (returned instanceof jIO.util.jIOError) {
return returned;
}
data = returned[0]; data = returned[0];
user_id = user_dict[returned[1]]; user_id = user_dict[returned[1]];
if (data.error === 'err_unauthorized') { data = [data];
throw new jIO.util.jIOError('Invalid token provided, Unauthorized.', if (!isId) {
401); if (data[0]._metadata === undefined) {
} return new jIO.util.jIOError('Malformed Automatic API result.', 500);
if (!checkEndpointAsId(endpoint)) {
if (data._metadata === undefined) {
throw new jIO.util.jIOError('Malformed Automatic API result.', 500);
} }
next = data._metadata.next; next = data[0]._metadata.next;
data = data.results; data = data[0].results;
}
for (i = 0; i < data.length; i += 1) {
path = URI(data[i].url).path();
temp = {
'path': path,
'reference': user_id + path,
'type': type,
'started_at': data[i].started_at || null,
'ended_at': data[i].ended_at || null,
'user': user_id
};
result.push(temp);
jio._cache.put('/' + user_id + path, temp);
jio._cache.putAttachment('/' + user_id + path, 'data',
new Blob([JSON.stringify(data[i].results)], {type:
'application/text'}));
} }
if (next === undefined) { return RSVP.Queue().push(function () {
return true; return RSVP.all(data.map(function (dat) {
} var path, temp;
return jIO.util.ajax({ path = URI(dat.url).path();
'type': 'GET', temp = {
'url': data._metadata.next, 'path': path,
'headers': {'Authorization': 'Bearer ' + returned[1]}, 'reference': '/' + user_id + path,
'xhrFields': {withCredentials: true} 'id': '/' + user_id + path,
}).push(function (response) { 'type': type,
if (response.target.status >= 400) { 'started_at': dat.started_at || null,
throw new jIO.util.jIOError( 'ended_at': dat.ended_at || null,
response.target.responseText ? 'user': user_id
JSON.parse(response.target.responseText) : {}, };
result.push(temp);
return jio._cache.put('/' + user_id + path, temp).push(function () {
return jio._cache.putAttachment('/' + user_id + path, 'data',
new Blob([JSON.stringify(dat)], {type:
'text/plain'}));
});
}));
}).push(function () {
if (next === undefined || next === null) {
return true;
}
return RSVP.Queue().push(function () {
return jIO.util.ajax({
'type': 'GET',
'url': next,
'headers': {'Authorization': 'Bearer ' + returned[1]}
//'xhrFields': {withCredentials: true}
});
}).push(function (response) {
return [JSON.parse(response.target.responseText), returned[1]];
}, function (response) {
return new jIO.util.jIOError(
response.target.responseText,
response.target.status response.target.status
); );
} }).push(treatNext);
return [JSON.parse(response.target.responseText), returned[1]]; }, function (error) {
}).push(treatNext); return error;
});
} }
// Start of the promise chain per token. // Start of the promise chain per token.
promises = jio._access_tokens.map(function (token) { promises = jio._access_tokens.map(function (token) {
return RSVP.Queue().push(function () { return new RSVP.Queue().push(function () {
return jIO.util.ajax({ return jIO.util.ajax({
'type': 'GET', 'type': 'GET',
'url': automatic_template.expand('/user/me/'), 'url': automatic_template.expand({endpoint: ['user', 'me', '']}),
'headers': {'Authorization': 'Bearer ' + token}, 'headers': {'Authorization': 'Bearer ' + token}
'xhrFields': {withCredentials: true} //'xhrFields': {withCredentials: true}
}).push(function (usr) { });
user_dict[token] = JSON.parse(usr).id; }).push(function (respusr) {
temp = endpoint.split('/')[1]; user_dict[token] = JSON.parse(respusr.target.responseText).id;
if (temp === 'all' || temp === user_dict[token]) { if (usr === 'all' || usr === user_dict[token]) {
return jIO.util.ajax({ return jIO.util.ajax({
'type': 'GET', 'type': 'GET',
'url': automatic_template.search(filters).expand(endpoint), 'url': URI(automatic_template.expand({endpoint:
'headers': {'Authorization': 'Bearer ' + token}, endpoint.split('/').splice(1)})).search(filters).toString(),
'xhrFields': {withCredentials: true} 'headers': {'Authorization': 'Bearer ' + token}
}); //'xhrFields': {withCredentials: true}
} });
throw new jIO.util.jIOError('Ignored.', 200); }
}).push(function (response) { return new jIO.util.jIOError('Ignored.', 200);
return [JSON.parse(response.target.responseText), token]; }).push(function (response) {
}).push(treatNext); if (response instanceof jIO.util.jIOError) {
}); return response;
}
return [JSON.parse(response.target.responseText), token];
}, function (response) {
return new jIO.util.jIOError(
response.target.responseText,
response.target.status
);
}).push(treatNext);
}); });
return RSVP.all(promises).push(function () { return new RSVP.Queue().push(function () {
return RSVP.all(promises);
}).push(function (trueOrErrorArray) {
// If we queried an id, return results should be length 1
if (isId && (usr !== 'all')) {
if (result.length === 1) {
return result[0];
}
if (result.length > 1) {
throw new jIO.util.jIOError('Automatic API returned more' +
' than one result for id endpoint', 500);
}
// Result is empty, so we throw the correct token error.
i = jio._access_tokens.map(function (token) {
if (user_dict[token] === usr) {
return true;
}
return false;
}).indexOf(true);
if (i < trueOrErrorArray.length && i > -1 &&
trueOrErrorArray[i] instanceof jIO.util.jIOError) {
throw trueOrErrorArray[i];
}
// If we didn't find the error in the promise returns, we don't have
// a token for user usr.
throw new jIO.util.jIOError('No valid token for user: ' + usr, 400);
}
// Otherwise return results and errors and let caller handle.
return result; return result;
}); });
} }
...@@ -171,6 +218,11 @@ ...@@ -171,6 +218,11 @@
} }
AutomaticAPIStorage.prototype.get = function (id) { AutomaticAPIStorage.prototype.get = function (id) {
var self = this,
endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) { if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id + throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400); ' is forbidden (not starting with /)', 400);
...@@ -179,20 +231,22 @@ ...@@ -179,20 +231,22 @@
throw new jIO.util.jIOError('id ' + id + throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400); ' is forbidden (not ending with /)', 400);
} }
if (!checkEndpointAsId(id)) { if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400); throw new jIO.util.jIOError('Invalid id.', 400);
} }
return this._cache.get(id).push(function (res) { return this._cache.get(id).push(function (res) {
return res; return res;
}, function () { }, function () {
return _queryAutomaticAPI(id, {}, this).push(function (res) { return _queryAutomaticAPI(endpoint, {'user': usr}, self)
if (typeof res === 'object' && res.length > 0) { .push(function (res) {
return res[0]; return res;
} }, function (err) {
throw new jIO.util.jIOError('Cannot find document: ' + id, 404); throw new jIO.util.jIOError('Cannot find document: ' + id +
}, function () { ', Error: ' + err.message, 404);
throw new jIO.util.jIOError('Cannot find document: ' + id, 404); });
});
}); });
}; };
...@@ -208,6 +262,36 @@ ...@@ -208,6 +262,36 @@
return; return;
}; };
AutomaticAPIStorage.prototype.getAttachment = function (id, name, options) {
var self = this,
endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400);
}
if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400);
}
return this._cache.get(id).push(function () {
return self._cache.getAttachment(id, name, options);
}, function () {
return self.get(id)
.push(function () {
return self._cache.getAttachment(id, name, options);
});
});
};
AutomaticAPIStorage.prototype.putAttachment = function () { AutomaticAPIStorage.prototype.putAttachment = function () {
return; return;
}; };
...@@ -216,6 +300,28 @@ ...@@ -216,6 +300,28 @@
return; return;
}; };
AutomaticAPIStorage.prototype.allAttachments = function (id) {
var endpoint = id.split('/'),
usr;
usr = endpoint.splice(1, 1)[0];
endpoint = endpoint.join('/');
if (id.indexOf('/') !== 0) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not starting with /)', 400);
}
if (id.lastIndexOf("/") !== (id.length - 1)) {
throw new jIO.util.jIOError('id ' + id +
' is forbidden (not ending with /)', 400);
}
if (!checkEndpointAsId(endpoint)) {
throw new jIO.util.jIOError('Invalid id.', 400);
}
if (usr === 'all') {
throw new jIO.util.jIOError('Invalid id.', 400);
}
return {data: null};
};
AutomaticAPIStorage.prototype.repair = function () { AutomaticAPIStorage.prototype.repair = function () {
return; return;
}; };
...@@ -224,24 +330,27 @@ ...@@ -224,24 +330,27 @@
return ((name === 'list') || (name === 'query')); return ((name === 'list') || (name === 'query'));
}; };
AutomaticAPIStorage.prototype.buildQuery = function (query) { AutomaticAPIStorage.prototype.buildQuery = function (options) {
var parsed_query = jIO.QueryFactory.create(query), var parsed_query = jIO.QueryFactory.create(options.query),
key_list, key_list,
automatic_filters = {}, automatic_filters = {},
simplequery_type_value, simplequery_type_value,
intercept_keys = ['started_at', 'ended_at', 'vehicle'], intercept_keys = ['started_at', 'ended_at', 'user', 'vehicle'],
intercept_accepted_operators = [['>', '>=', '<', '<='], intercept_accepted_operators = [['>', '>=', '<', '<='],
['>', '>=', '<', '<='], ['=']], ['>', '>=', '<', '<='], ['='], ['=']],
temp_operator_index, temp_operator_index,
temp_key, temp_key,
automatic_endpoint, automatic_endpoint,
i, i,
j; j;
// remove query from the options
delete options.query;
// XXX: check if there is no built-in method to seek in queries for // XXX: check if there is no built-in method to seek in queries for
// specific keys... // specific keys...
function extractKeysFromQuery(quer) { function extractKeysFromQuery(quer) {
var keys_list = []; var keys_list = [],
i;
if (quer.type === 'complex') { if (quer.type === 'complex') {
for (i = 0; i < quer.query_list.length; i += 1) { for (i = 0; i < quer.query_list.length; i += 1) {
keys_list = keys_list.concat( keys_list = keys_list.concat(
...@@ -254,12 +363,13 @@ ...@@ -254,12 +363,13 @@
return keys_list; return keys_list;
} }
function findSimpleQueryForKey(quer, key) { function findSimpleQueryForKey(key, quer) {
var temp, var temp,
result = []; result = [],
i;
if (quer.type === 'complex') { if (quer.type === 'complex') {
for (i = 0; i < quer.query_list.length; i += 1) { for (i = 0; i < quer.query_list.length; i += 1) {
temp = findSimpleQueryForKey(quer.query_list[i], key); temp = findSimpleQueryForKey(key, quer.query_list[i]);
if (temp !== undefined) { if (temp !== undefined) {
result = result.concat(temp); result = result.concat(temp);
} }
...@@ -285,7 +395,9 @@ ...@@ -285,7 +395,9 @@
if (i < 2) { if (i < 2) {
temp_key = intercept_keys[i] + temp_key = intercept_keys[i] +
(temp_operator_index < 2 ? '__gte' : '__lte'); (temp_operator_index < 2 ? '__gte' : '__lte');
automatic_filters[temp_key] = simplequery_type_value[j][1]; automatic_filters[temp_key] = (new Date(
simplequery_type_value[j][1]
).getTime() / 1000).toString();
} else { } else {
temp_key = intercept_keys[i]; temp_key = intercept_keys[i];
automatic_filters[temp_key] = simplequery_type_value[j][1]; automatic_filters[temp_key] = simplequery_type_value[j][1];
...@@ -311,7 +423,13 @@ ...@@ -311,7 +423,13 @@
automatic_endpoint = '/' + simplequery_type_value[0][1] + '/'; automatic_endpoint = '/' + simplequery_type_value[0][1] + '/';
return _queryAutomaticAPI(automatic_endpoint, automatic_filters, this) return _queryAutomaticAPI(automatic_endpoint, automatic_filters, this)
.push(function (results) { .push(function (results) {
return parsed_query.exec(results); if (!(results instanceof Array)) {
results = [results];
}
return parsed_query.exec(results, options);
}); });
}; };
}(jIO, RSVP, Blob, UriTemplate));
\ No newline at end of file jIO.addStorage('automaticapi', AutomaticAPIStorage);
}(jIO, RSVP, UriTemplate, URI, Blob));
\ No newline at end of file
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
this.jio = jIO.createJIO({ this.jio = jIO.createJIO({
type: "automaticapi", type: "automaticapi",
access_token: tokens access_tokens: tokens
}); });
}, },
teardown: function () { teardown: function () {
...@@ -85,13 +85,25 @@ ...@@ -85,13 +85,25 @@
}); });
test("get inexistent document", function () { test("get inexistent document", function () {
var url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "usertest"}' ]);
url = "https://api.automatic.com/trip/T_inexistent/";
this.server.respondWith("GET", url, [404, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"error": "err_object_not_found"}' ]);
stop(); stop();
expect(3); expect(3);
this.jio.get("/inexistent/") this.jio.get("/usertest/trip/T_inexistent/")
.fail(function (error) { .fail(function (error) {
ok(error instanceof jIO.util.jIOError); ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: /inexistent/"); equal(error.message, 'Cannot find document:' +
' /usertest/trip/T_inexistent/, Error: ' +
'{"error": "err_object_not_found"}');
equal(error.status_code, 404); equal(error.status_code, 404);
}) })
.fail(function (error) { .fail(function (error) {
...@@ -102,19 +114,15 @@ ...@@ -102,19 +114,15 @@
}); });
}); });
test("handle unauthorized", function () { test("get invalid id document", function () {
var url = "https://api.automatic.com/vehicle/";
this.server.respondWith("GET", url, [401, {
"Content-Type": "application/json"
}, '{"error":"err_unauthorized","detail":"Invalid token."}\n']);
stop(); stop();
expect(3); expect(3);
this.jio.get("/0/vehicle/") this.jio.get("/inexistent/")
.fail(function (error) { .fail(function (error) {
ok(error instanceof jIO.util.jIOError); ok(error instanceof jIO.util.jIOError);
equal(error.message, "Invalid token provided, Unauthorized."); equal(error.message, "Invalid id.");
equal(error.status_code, 401); equal(error.status_code, 400);
}) })
.fail(function (error) { .fail(function (error) {
ok(false, error); ok(false, error);
...@@ -124,31 +132,49 @@ ...@@ -124,31 +132,49 @@
}); });
}); });
test("get list of something", function () { test("get on /all/... is invalid", function () {
var url = "https://api.automatic.com/vehicle/"; var url = "https://api.automatic.com/trip/T_whatever/";
this.server.respondWith("GET", url, [200, { this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json" "Content-Type": "application/json"
}, '{"_metadata":{"count":1,"next":null,"previous":null},' + }, '{}\n']);
'"results":[{"id": "V_example"}]}' ]);
url = "https://api.automatic.com/users/me/"; stop();
expect(3);
this.jio.get("/all/trip/T_whatever/")
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Invalid id.");
equal(error.status_code, 400);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("handle unauthorized in Automatic", function () {
var url = "https://api.automatic.com/trip/T_whatever/";
this.server.respondWith("GET", url, [401, {
"Content-Type": "application/json"
}, '{"error":"err_unauthorized","detail":"Invalid token."}\n']);
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, { this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip", "Content-Encoding": "gzip",
"Content-Type": "application/json" "Content-Type": "application/json"
}, '{"id": "0"}' ]); }, '{"id": "usertest2"}' ]);
stop(); stop();
expect(1); expect(3);
this.jio.get("/all/vehicle/") this.jio.get("/usertest/trip/T_whatever/")
.then(function (result) { .fail(function (error) {
deepEqual(result, [{ ok(error instanceof jIO.util.jIOError);
'path': '/vehicle/V_example/', equal(error.message, "Cannot find document: /usertest/trip/T_whatever/"
'reference': '/0/vehicle/V_example/', + ", Error: No valid token for user: usertest");
'type': 'vehicle', equal(error.status_code, 404);
'started_at': null,
'ended_at': null,
'user': '0'
}], "Check list type");
}) })
.fail(function (error) { .fail(function (error) {
ok(false, error); ok(false, error);
...@@ -159,30 +185,39 @@ ...@@ -159,30 +185,39 @@
}); });
test("get single element", function () { test("get single element", function () {
var url = "https://api.automatic.com/trip" + var url = "https://api.automatic.com/trip/T_randomtrip/";
"/T_randomtrip"; this.server.respondWith(function (xhr) {
this.server.respondWith("GET", url, [200, { if (xhr.url !== 'https://api.automatic.com/trip/T_randomtrip/') {
'Content-Encoding': 'gzip', return;
'Content-Type': 'application/json' }
}, '{"id":"T_randomtrip",' + if (xhr.requestHeaders.Authorization &&
'"url":"https://api.automatic.com/trip/T_randomtrip/"}']); xhr.requestHeaders.Authorization === 'Bearer sample_token1') {
url = "https://api.automatic.com/users/me/"; xhr.respond(200, { "Content-Type": "application/json" },
'{"id":"T_randomtrip",' +
'"url":"https://api.automatic.com/trip/T_randomtrip/"}\n');
return;
}
xhr.respond(404, { "Content-Type": "application/json" },
'{"error":"err_unauthorized","detail":"Invalid token."}\n');
});
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, { this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip", "Content-Encoding": "gzip",
"Content-Type": "application/json" "Content-Type": "application/json"
}, '{"id": "0"}' ]); }, '{"id": "usertest"}' ]);
stop(); stop();
expect(1); expect(1);
this.jio.get("/0/trip/T_randomtrip") this.jio.get("/usertest/trip/T_randomtrip/")
.then(function (result) { .then(function (result) {
deepEqual(result, { deepEqual(result, {
'path': '/trip/T_randomtrip/', 'path': '/trip/T_randomtrip/',
'reference': '/0/trip/T_randomtrip/', 'reference': '/usertest/trip/T_randomtrip/',
'id': '/usertest/trip/T_randomtrip/',
'type': 'trip', 'type': 'trip',
'started_at': null, 'started_at': null,
'ended_at': null, 'ended_at': null,
'user': '0' 'user': 'usertest'
}, "Check single element type"); }, "Check single element type");
}) })
.fail(function (error) { .fail(function (error) {
...@@ -193,10 +228,230 @@ ...@@ -193,10 +228,230 @@
}); });
}); });
/////////////////////////////////////////////////////////////////
// AutomaticAPIStorage.getAttachment
/////////////////////////////////////////////////////////////////
module("AutomaticAPIStorage.getAttachment", {
setup: function () {
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
this.server.autoRespondAfter = 5;
this.jio = jIO.createJIO({
type: "automaticapi",
access_tokens: tokens
});
},
teardown: function () {
this.server.restore();
delete this.server;
}
});
test("reject ID not starting with /", function () {
stop();
expect(3);
this.jio.getAttachment("get1/", "whatever")
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "id get1/ is forbidden (not starting with /)");
equal(error.status_code, 400);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("reject ID not ending with /", function () {
stop();
expect(3);
this.jio.getAttachment("/get1", 'whatever')
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "id /get1 is forbidden (not ending with /)");
equal(error.status_code, 400);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get inexistent document's attachment", function () {
var url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "usertest"}' ]);
url = "https://api.automatic.com/trip/T_inexistent/";
this.server.respondWith("GET", url, [404, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"error": "err_object_not_found"}' ]);
stop();
expect(3);
this.jio.getAttachment("/usertest/trip/T_inexistent/", 'whatever')
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, 'Cannot find document:' +
' /usertest/trip/T_inexistent/, Error: ' +
'{"error": "err_object_not_found"}');
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get invalid id document's attachment", function () {
stop();
expect(3);
this.jio.getAttachment("/inexistent/", 'whatever')
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Invalid id.");
equal(error.status_code, 400);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("getAttachment on /all/... is invalid", function () {
var url = "https://api.automatic.com/trip/T_whatever/";
this.server.respondWith("GET", url, [200, {
"Content-Type": "application/json"
}, '{}\n']);
stop();
expect(3);
this.jio.getAttachment("/all/trip/T_whatever/", 'whatever')
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Invalid id.");
equal(error.status_code, 400);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("handle unauthorized in Automatic", function () {
var url = "https://api.automatic.com/trip/T_whatever/";
this.server.respondWith("GET", url, [401, {
"Content-Type": "application/json"
}, '{"error":"err_unauthorized","detail":"Invalid token."}\n']);
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "usertest2"}' ]);
stop();
expect(3);
this.jio.getAttachment("/usertest/trip/T_whatever/", 'whatever')
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: /usertest/trip/T_whatever/"
+ ", Error: No valid token for user: usertest");
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("getAttachment on single element", function () {
var url = "https://api.automatic.com/trip/T_randomtrip/";
this.server.respondWith(function (xhr) {
if (xhr.url !== 'https://api.automatic.com/trip/T_randomtrip/') {
return;
}
if (xhr.requestHeaders.Authorization &&
xhr.requestHeaders.Authorization === 'Bearer sample_token1') {
xhr.respond(200, { "Content-Type": "application/json" },
'{"id":"T_randomtrip",' +
'"url":"https://api.automatic.com/trip/T_randomtrip/"}\n');
return;
}
xhr.respond(404, { "Content-Type": "application/json" },
'{"error":"err_unauthorized","detail":"Invalid token."}\n');
});
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "usertest"}' ]);
stop();
expect(1);
this.jio.getAttachment("/usertest/trip/T_randomtrip/", 'data', {format:
'text'}).then(function (result) {
deepEqual(result, '{"id":"T_randomtrip",' +
'"url":"https://api.automatic.com/trip/T_randomtrip/"}',
"Check single element type");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
/////////////////////////////////////////////////////////////////
// AutomaticAPIStorage.allAttachments
/////////////////////////////////////////////////////////////////
module("AutomaticAPIStorage.allAttachments", {
setup: function () {
this.jio = jIO.createJIO({
type: "automaticapi",
access_tokens: tokens
});
}
});
test("only attachment is data", function () {
stop();
expect(1);
this.jio.allAttachments('/usertest/trip/T_trip/').then(function (result) {
deepEqual(result, {data: null});
}).fail(function (error) {
ok(false, error);
}).always(function () {
start();
});
});
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// AutomaticAPIStorage.buildQuery // AutomaticAPIStorage.buildQuery
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
module("queryStorage.buildQuery", { module("AutomaticAPIStorage.buildQuery", {
setup: function () { setup: function () {
this.server = sinon.fakeServer.create(); this.server = sinon.fakeServer.create();
...@@ -205,7 +460,7 @@ ...@@ -205,7 +460,7 @@
this.jio = jIO.createJIO({ this.jio = jIO.createJIO({
type: "automaticapi", type: "automaticapi",
access_token: tokens access_tokens: tokens
}); });
}, },
teardown: function () { teardown: function () {
...@@ -215,14 +470,25 @@ ...@@ -215,14 +470,25 @@
}); });
test("query started_at, ended_at taken into account", function () { test("query started_at, ended_at taken into account", function () {
var url = "https://api.automatic.com/trip/"; var url;
this.server.respondWith("GET", url, [200, { this.server.respond(function (xhr) {
'Content-Encoding': 'gzip', if (xhr.url.indexOf('https://api.automatic.com/trip/') === -1) {
'Content-Type': 'application/json' return;
}, '{"_metadata":{"count":1,"next":null,"previous":null},' + }
'"results":[{"id": "T_example", "started_at": "2017-06-17T16:45:41Z"' + if (xhr.requestHeaders.Authorization &&
', "ended_at": "2017-06-17T16:46:38Z"}]}']); xhr.requestHeaders.Authorization === 'Bearer sample_token1') {
url = "https://api.automatic.com/users/me/"; xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":1,"next":null,"previous":null},' +
'"results":[{"id": "T_example", "started_at": "2017-06-17T16:45:41Z"'
+ ', "ended_at": "2017-06-17T16:46:38Z"' +
', "url": "https://api.automatic.com/trip/T_example/"}]}');
return;
}
xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":0,"next":null,"previous":null},' +
'"results":[]}');
});
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, { this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip", "Content-Encoding": "gzip",
"Content-Type": "application/json" "Content-Type": "application/json"
...@@ -230,7 +496,9 @@ ...@@ -230,7 +496,9 @@
stop(); stop();
expect(4); expect(4);
this.jio.buildQuery('started_at > "2017-06-17T18:45:41Z"') this.jio.buildQuery({
query: 'started_at:>"2017-06-17T18:45:41Z" AND type:="trip"'
})
.then(function (result) { .then(function (result) {
deepEqual(result, [], "Check no result"); deepEqual(result, [], "Check no result");
}) })
...@@ -240,11 +508,14 @@ ...@@ -240,11 +508,14 @@
.always(function () { .always(function () {
start(); start();
}); });
this.jio.buildQuery('started_at > "2017-06-17T14:45:41Z"') this.jio.buildQuery({
query: 'started_at:>"2017-06-17T14:45:41Z" AND type:="trip"'
})
.then(function (result) { .then(function (result) {
deepEqual(result, [{ deepEqual(result, [{
'path': '/trip/T_example/', 'path': '/trip/T_example/',
'reference': '/0/trip/T_example/', 'reference': '/0/trip/T_example/',
'id': '/0/trip/T_example/',
'type': 'trip', 'type': 'trip',
'started_at': "2017-06-17T16:45:41Z", 'started_at': "2017-06-17T16:45:41Z",
'ended_at': "2017-06-17T16:46:38Z", 'ended_at': "2017-06-17T16:46:38Z",
...@@ -254,24 +525,35 @@ ...@@ -254,24 +525,35 @@
.fail(function (error) { .fail(function (error) {
ok(false, error); ok(false, error);
}) })
.always(function () {
stop();
})
.always(function () { .always(function () {
start(); start();
}); });
this.jio.buildQuery('ended_at > "2017-06-17T18:45:41Z"') this.jio.buildQuery({
query: 'ended_at:>"2017-06-17T18:45:41Z" AND type:="trip"'
})
.then(function (result) { .then(function (result) {
deepEqual(result, [], "Check no result"); deepEqual(result, [], "Check no result");
}) })
.fail(function (error) { .fail(function (error) {
ok(false, error); ok(false, error);
}) })
.always(function () {
stop();
})
.always(function () { .always(function () {
start(); start();
}); });
this.jio.buildQuery('ended_at < "2017-06-17T18:45:41Z"') this.jio.buildQuery({
query: 'ended_at:<"2017-06-17T18:45:41Z" AND type:="trip"'
})
.then(function (result) { .then(function (result) {
deepEqual(result, [{ deepEqual(result, [{
'path': '/trip/T_example/', 'path': '/trip/T_example/',
'reference': '/0/trip/T_example/', 'reference': '/0/trip/T_example/',
'id': '/0/trip/T_example/',
'type': 'trip', 'type': 'trip',
'started_at': "2017-06-17T16:45:41Z", 'started_at': "2017-06-17T16:45:41Z",
'ended_at': "2017-06-17T16:46:38Z", 'ended_at': "2017-06-17T16:46:38Z",
...@@ -281,10 +563,125 @@ ...@@ -281,10 +563,125 @@
.fail(function (error) { .fail(function (error) {
ok(false, error); ok(false, error);
}) })
.always(function () {
stop();
})
.always(function () {
start();
});
});
test("get list of something", function () {
var url;
this.server.respond(function (xhr) {
if (xhr.url.indexOf('https://api.automatic.com/vehicle/') === -1) {
return;
}
if (xhr.requestHeaders.Authorization &&
xhr.requestHeaders.Authorization === 'Bearer sample_token1') {
xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":1,"next":null,"previous":null},' +
'"results":[{"id": "V_example", ' +
' "url": "https://api.automatic.com/vehicle/V_example/"}]}');
return;
}
xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":0,"next":null,"previous":null},' +
'"results":[]}');
});
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "0"}' ]);
stop();
expect(1);
this.jio.buildQuery({
query: 'type:="vehicle"'
})
.then(function (result) {
deepEqual(result, [{
'path': '/vehicle/V_example/',
'reference': '/0/vehicle/V_example/',
'id': '/0/vehicle/V_example/',
'type': 'vehicle',
'started_at': null,
'ended_at': null,
'user': '0'
}], "Check vehicle list is returned");
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("get list of something, check that next url works", function () {
var url;
this.server.respond(function (xhr) {
if (xhr.url.indexOf('https://api.automatic.com/vehicle/') === -1) {
return;
}
if (xhr.requestHeaders.Authorization &&
xhr.requestHeaders.Authorization === 'Bearer sample_token1') {
xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":1,"next": ' +
'"https://api.automatic.com/specific/nexturl/","previous":null},' +
'"results":[{"id": "V_example", ' +
' "url": "https://api.automatic.com/vehicle/V_example/"}]}');
return;
}
xhr.respond(200, { "Content-Type": "application/json" },
'{"_metadata":{"count":0,"next":null,"previous":null},' +
'"results":[]}');
});
url = "https://api.automatic.com/user/me/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"id": "0"}' ]);
url = "https://api.automatic.com/specific/nexturl/";
this.server.respondWith("GET", url, [200, {
"Content-Encoding": "gzip",
"Content-Type": "application/json"
}, '{"_metadata":{"count":1,"next":null,"previous":null},' +
'"results":[{"id": "V_example2", ' +
' "url": "https://api.automatic.com/vehicle/V_example2/"}]}' ]);
stop();
expect(1);
this.jio.buildQuery({
query: 'type:="vehicle"'
})
.then(function (result) {
deepEqual(result, [{
'path': '/vehicle/V_example/',
'reference': '/0/vehicle/V_example/',
'id': '/0/vehicle/V_example/',
'type': 'vehicle',
'started_at': null,
'ended_at': null,
'user': '0'
}, {
'path': '/vehicle/V_example2/',
'reference': '/0/vehicle/V_example2/',
'id': '/0/vehicle/V_example2/',
'type': 'vehicle',
'started_at': null,
'ended_at': null,
'user': '0'
}], "Check vehicle list is returned");
})
.fail(function (error) {
ok(false, error);
})
.always(function () { .always(function () {
start(); start();
}); });
}); });
}(jIO, QUnit, Blob, sinon)); }(jIO, QUnit, sinon));
\ No newline at end of file \ No newline at end of file
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
<script src="jio.storage/replicatestorage_fastrepairattachment.tests.js"></script> <script src="jio.storage/replicatestorage_fastrepairattachment.tests.js"></script>
<script src="jio.storage/shastorage.tests.js"></script> <script src="jio.storage/shastorage.tests.js"></script>
<script src="jio.storage/mappingstorage.tests.js"></script> <script src="jio.storage/mappingstorage.tests.js"></script>
<script src="jio.storage/automaticapistorage.tests.js"></script>
<!--script src="jio.storage/qiniustorage.tests.js"></script--> <!--script src="jio.storage/qiniustorage.tests.js"></script-->
<!--script src="jio.storage/indexstorage.tests.js"></script--> <!--script src="jio.storage/indexstorage.tests.js"></script-->
<script src="jio.storage/cryptstorage.tests.js"></script> <script src="jio.storage/cryptstorage.tests.js"></script>
...@@ -52,7 +53,6 @@ ...@@ -52,7 +53,6 @@
<script src="jio.storage/zipstorage.tests.js"></script> <script src="jio.storage/zipstorage.tests.js"></script>
<script src="jio.storage/gdrivestorage.tests.js"></script> <script src="jio.storage/gdrivestorage.tests.js"></script>
<script src="jio.storage/websqlstorage.tests.js"></script> <script src="jio.storage/websqlstorage.tests.js"></script>
<script src="jio.storage/fbstorage.tests.js"></script>
<!--script src="../lib/jquery/jquery.min.js"></script> <!--script src="../lib/jquery/jquery.min.js"></script>
<script src="../src/jio.storage/xwikistorage.js"></script> <script src="../src/jio.storage/xwikistorage.js"></script>
<script src="jio.storage/xwikistorage.tests.js"></script--> <script src="jio.storage/xwikistorage.tests.js"></script-->
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment