Skip to content

Commit c3f8e79

Browse files
author
Mark Moffat
committed
[Fix mrvautin#31] - Can now edit/insert BSON formatted documents.
1 parent 6c258d7 commit c3f8e79

File tree

8 files changed

+282
-29
lines changed

8 files changed

+282
-29
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"jquery": "^2.2.0",
4040
"mongo-uri": "^0.1.2",
4141
"mongodb": "^2.1.4",
42+
"mongodb-extended-json": "^1.6.2",
4243
"mongojs": "^2.2.2",
4344
"morgan": "~1.6.1",
4445
"nconf": "^0.8.2",

public/js/editor.js

+5-19
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ $(document).ready(function() {
33
editor.setTheme("ace/theme/github");
44
editor.session.setMode("ace/mode/json");
55
editor.setFontSize(14);
6+
editor.getSession().setUseWorker(false);
67
editor.$blockScrolling = Infinity;
78

89
$("#submit_json").click(function() {
910
try {
11+
// convert BSON string to EJSON
12+
var ejson = toEJSON.serializeString(editor.getValue());
13+
1014
$.ajax({
1115
method: "POST",
1216
contentType: 'application/json',
1317
url: "/" + $("#conn_name").val() + "/" + $("#db_name").val() + "/" + $("#coll_name").val() + "/" + $("#edit_request_type").val(),
14-
data: editor.getValue()
18+
data: JSON.stringify({ "objectData": ejson})
1519
})
1620
.success(function(msg) {
1721
show_notification(msg,"success");
@@ -24,22 +28,4 @@ $(document).ready(function() {
2428
show_notification(err,"danger");
2529
}
2630
});
27-
28-
// enable/disable submit button if errors
29-
editor.getSession().on("changeAnnotation", function(){
30-
var annot = editor.getSession().getAnnotations();
31-
if(annot.length > 0){
32-
$("#submit_json").prop('disabled', true);
33-
$("#queryDocumentsAction").prop('disabled', true);
34-
}else{
35-
$("#submit_json").prop('disabled', false);
36-
$("#queryDocumentsAction").prop('disabled', false);
37-
}
38-
});
39-
40-
41-
// prettify the json
42-
var jsonString = editor.getValue();
43-
var jsonPretty = JSON.stringify(JSON.parse(jsonString),null,2);
44-
editor.setValue(jsonPretty);
4531
});

public/js/toEJSON.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
var toEJSON = (function () {
2+
3+
var serialize_BinData = function (bsonString) {
4+
var bson_full = bsonString.match(/(BinData\s?\([^)]+\))/g);
5+
if (bson_full) {
6+
for (var i = 0; i < bson_full.length; i++) {
7+
var bson_value = bson_full[i].match(/\((.*?)\)/i);
8+
var bson_data = bson_value[1].split(',');
9+
var ejson = '{ "$binary": ' + bson_data[1] + ', "$type": "' + bson_data[0] + '" }';
10+
bsonString = bsonString.replace(/(BinData\s?\([^)]+\))/g, ejson);
11+
}
12+
}
13+
return bsonString;
14+
};
15+
16+
var serialize_Date = function (bsonString) {
17+
var bson_full = bsonString.match(/(new Date\s?)\(.?\)/g);
18+
if (bson_full) {
19+
for (var i = 0; i < bson_full.length; i++) {
20+
var dte = new Date();
21+
var ejson = '{ "$date": "' + dte.toISOString() + '" }';
22+
bsonString = bsonString.replace(/(new Date\s?)\(.?\)/g, ejson);
23+
}
24+
}
25+
return bsonString;
26+
};
27+
28+
var serialize_ISODate = function (bsonString) {
29+
var bson_full = bsonString.match(/(ISODate\s?\([^)]+\))/g);
30+
if (bson_full) {
31+
for (var i = 0; i < bson_full.length; i++) {
32+
var bson_value = bson_full[i].match(/\((.*?)\)/i);
33+
var ejson = '{ "$date": ' + bson_value[1] + ' }';
34+
bsonString = bsonString.replace(/(ISODate\s?\([^)]+\))/g, ejson);
35+
}
36+
}
37+
return bsonString;
38+
};
39+
40+
var serialize_Timestamp = function (bsonString) {
41+
var bson_full = bsonString.match(/(Timestamp\s?\([^)]+\))/g);
42+
if (bson_full) {
43+
for (var i = 0; i < bson_full.length; i++) {
44+
var bson_value = bson_full[i].match(/\((.*?)\)/i);
45+
var bson_data = bson_value[1].split(',');
46+
var ejson = '{ "$timestamp": { "$t": ' + bson_data[0] + ', "$i": ' + bson_data[1] + '}}';
47+
bsonString = bsonString.replace(/(Timestamp\s?\([^)]+\))/g, ejson);
48+
}
49+
}
50+
return bsonString;
51+
};
52+
53+
var serialize_Regex = function (bsonString) {
54+
// TODO: Implement a regex fixer
55+
return bsonString;
56+
};
57+
58+
var serialize_ObjectId = function (bsonString) {
59+
var bson_full = bsonString.match(/(ObjectId\s?\([^)]+\))/g);
60+
if (bson_full) {
61+
for (var i = 0; i < bson_full.length; i++) {
62+
var bson_value = bson_full[i].match(/\((.*?)\)/i);
63+
var ejson = '{ "$oid": ' + bson_value[1] + '}';
64+
bsonString = bsonString.replace(/(ObjectId\s?\([^)]+\))/g, ejson);
65+
}
66+
}
67+
return bsonString;
68+
};
69+
70+
var serialize_DBRef = function (bsonString) {
71+
// TODO: possibly implement something in the future here
72+
return bsonString;
73+
};
74+
75+
var serializeString = function (bsonString) {
76+
77+
bsonString = serialize_BinData(bsonString);
78+
bsonString = serialize_Date(bsonString);
79+
bsonString = serialize_ISODate(bsonString);
80+
bsonString = serialize_Timestamp(bsonString);
81+
bsonString = serialize_Regex(bsonString);
82+
bsonString = serialize_ObjectId(bsonString);
83+
bsonString = serialize_DBRef(bsonString);
84+
85+
var eJsonString = bsonString;
86+
return eJsonString;
87+
};
88+
89+
return {
90+
serializeString: serializeString,
91+
};
92+
})();

routes/bsonify.js

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Basically ripped from https://github.com/douglascrockford/JSON-js/blob/master/json2.js but added BSON support
2+
3+
'use strict';
4+
5+
var mongodb = require('mongodb');
6+
7+
function f(n) {
8+
// Format integers to have at least two digits.
9+
return n < 10 ? '0' + n : n;
10+
}
11+
12+
// JSHint warning suppression
13+
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
14+
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
15+
var gap;
16+
var indent;
17+
var meta = { // table of character substitutions
18+
'\b': '\\b',
19+
'\t': '\\t',
20+
'\n': '\\n',
21+
'\f': '\\f',
22+
'\r': '\\r',
23+
'"': '\\"',
24+
'\\': '\\\\',
25+
};
26+
var rep;
27+
28+
function quote(string) {
29+
escapable.lastIndex = 0;
30+
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
31+
var c = meta[a];
32+
return typeof c === 'string' ?
33+
c :
34+
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
35+
}) + '"' : '"' + string + '"';
36+
}
37+
38+
function str(key, holder) {
39+
var i; // The loop counter.
40+
var k; // The member key.
41+
var v; // The member value.
42+
var length;
43+
var mind = gap;
44+
var partial;
45+
var value = holder[key];
46+
47+
if (value instanceof mongodb.ObjectID) {
48+
return 'ObjectId("' + value + '")';
49+
} else if (value instanceof mongodb.Timestamp) {
50+
return 'Timestamp(' + value.high_ + ', ' + value.low_ + ')';
51+
} else if (value instanceof Date) {
52+
return 'ISODate("' + value.toJSON() + '")';
53+
} else if (value instanceof mongodb.DBRef) {
54+
if (value.db === '') {
55+
return 'DBRef("' + value.namespace + '", "' + value.oid + '")';
56+
} else {
57+
return 'DBRef("' + value.namespace + '", "' + value.oid + '", "' + value.db + '")';
58+
}
59+
} else if (value instanceof mongodb.Code) {
60+
return 'Code("' + value.code + '")';
61+
} else if (value instanceof mongodb.MinKey) {
62+
return 'MinKey()';
63+
} else if (value instanceof mongodb.MaxKey) {
64+
return 'MaxKey()';
65+
} else if (value instanceof mongodb.Symbol) {
66+
return 'Symbol("' + value + '")';
67+
}
68+
69+
if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
70+
value = value.toJSON(key);
71+
}
72+
73+
if (typeof rep === 'function') {
74+
value = rep.call(holder, key, value);
75+
}
76+
77+
switch (typeof value) {
78+
case 'string':
79+
return quote(value);
80+
81+
case 'number':
82+
return isFinite(value) ? String(value) : 'null';
83+
84+
case 'boolean':
85+
case 'null':
86+
return String(value);
87+
case 'object':
88+
if (!value) {
89+
return 'null';
90+
}
91+
gap += indent;
92+
partial = [];
93+
if (Object.prototype.toString.apply(value) === '[object Array]') {
94+
length = value.length;
95+
for (i = 0; i < length; i += 1) {
96+
partial[i] = str(i, value) || 'null';
97+
}
98+
v = partial.length === 0 ?
99+
'[]' :
100+
gap ?
101+
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
102+
'[' + partial.join(',') + ']';
103+
gap = mind;
104+
return v;
105+
}
106+
if (rep && typeof rep === 'object') {
107+
length = rep.length;
108+
for (i = 0; i < length; i += 1) {
109+
if (typeof rep[i] === 'string') {
110+
k = rep[i];
111+
v = str(k, value);
112+
if (v) {
113+
partial.push(quote(k) + (gap ? ': ' : ':') + v);
114+
}
115+
}
116+
}
117+
} else {
118+
for (k in value) {
119+
if (Object.prototype.hasOwnProperty.call(value, k)) {
120+
v = str(k, value);
121+
if (v) {
122+
partial.push(quote(k) + (gap ? ': ' : ':') + v);
123+
}
124+
}
125+
}
126+
}
127+
v = partial.length === 0 ?
128+
'{}' :
129+
gap ?
130+
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
131+
'{' + partial.join(',') + '}';
132+
gap = mind;
133+
return v;
134+
}
135+
}
136+
137+
exports.stringify = function (value, replacer, space) {
138+
var i;
139+
gap = '';
140+
indent = '';
141+
if (typeof space === 'number') {
142+
for (i = 0; i < space; i += 1) {
143+
indent += ' ';
144+
}
145+
} else if (typeof space === 'string') {
146+
indent = space;
147+
}
148+
149+
rep = replacer;
150+
if (replacer && typeof replacer !== 'function' &&
151+
(typeof replacer !== 'object' ||
152+
typeof replacer.length !== 'number')) {
153+
throw new Error('JSON.stringify');
154+
}
155+
return str('', { '': value });
156+
};

routes/index.js

+25-8
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ router.get('/:conn/:db/:coll/edit/:doc_id', function (req, res, next) {
321321
var connection_list = req.nconf.get('connections');
322322
var mongodb = require('mongodb').MongoClient;
323323
var mongo_uri = require('mongo-uri');
324+
var bsonify = require('./bsonify');
324325

325326
// Check for existance of connection
326327
if(connection_list[req.params.conn] == undefined){
@@ -365,7 +366,7 @@ router.get('/:conn/:db/:coll/edit/:doc_id', function (req, res, next) {
365366
sidebar_list: sidebar_list,
366367
coll_name: req.params.coll,
367368
coll_list: collection_list.sort(),
368-
coll_doc: coll_doc,
369+
coll_doc: bsonify.stringify(coll_doc, null, ' '),
369370
helpers: helpers,
370371
editor: true
371372
});
@@ -743,6 +744,7 @@ router.post('/:conn/:db/:coll/insert_doc', function (req, res, next) {
743744
var mongojs = require('mongojs');
744745
var connection_list = req.nconf.get('connections');
745746
var mongodb = require('mongodb').MongoClient;
747+
var ejson = require('mongodb-extended-json');
746748

747749
// Check for existance of connection
748750
if(connection_list[req.params.conn] == undefined){
@@ -763,9 +765,18 @@ router.post('/:conn/:db/:coll/insert_doc', function (req, res, next) {
763765
res.end('Error connecting to database: ' + err);
764766
}else{
765767
var db = mongojs(mongo_db.db(req.params.db));
768+
769+
try {
770+
var eJsonData = ejson.parse(req.body.objectData);
771+
}catch (e) {
772+
console.error("Syntax error: " + e);
773+
res.writeHead(400, { 'Content-Type': 'application/text' });
774+
res.end('Syntax error. Please check the syntax');
775+
return;
776+
}
766777

767778
// adding a new doc
768-
db.collection(req.params.coll).save(req.body, function (err, docs) {
779+
db.collection(req.params.coll).save(eJsonData, function (err, docs) {
769780
if(err){
770781
console.error('Error inserting document: ' + err);
771782
res.writeHead(400, { 'Content-Type': 'application/text' });
@@ -783,6 +794,7 @@ router.post('/:conn/:db/:coll/edit_doc', function (req, res, next) {
783794
var mongojs = require('mongojs');
784795
var connection_list = req.nconf.get('connections');
785796
var mongodb = require('mongodb').MongoClient;
797+
var ejson = require('mongodb-extended-json');
786798

787799
// Check for existance of connection
788800
if(connection_list[req.params.conn] == undefined){
@@ -803,11 +815,17 @@ router.post('/:conn/:db/:coll/edit_doc', function (req, res, next) {
803815
res.end('Error connecting to database: ' + err);
804816
}else{
805817
var db = mongojs(mongo_db.db(req.params.db));
818+
819+
try {
820+
var eJsonData = ejson.parse(req.body.objectData);
821+
}catch (e) {
822+
console.error("Syntax error: " + e);
823+
res.writeHead(400, { 'Content-Type': 'application/text' });
824+
res.end('Syntax error. Please check the syntax');
825+
return;
826+
}
806827

807-
// remove the _id form the body object so we set in query
808-
var doc_id = req.body['_id'];
809-
delete req.body['_id'];
810-
db.collection(req.params.coll).update({_id: parse_doc_id(doc_id, typeof doc_id)},req.body, function (err, doc, lastErrorObject) {
828+
db.collection(req.params.coll).save(eJsonData, function (err, doc, lastErrorObject) {
811829
if(err){
812830
console.error("Error updating document: " + err);
813831
res.writeHead(400, { 'Content-Type': 'application/text' });
@@ -816,7 +834,7 @@ router.post('/:conn/:db/:coll/edit_doc', function (req, res, next) {
816834
if(doc['nModified'] == 0){
817835
console.error('Error updating document: Document ID is incorrect');
818836
res.writeHead(400, { 'Content-Type': 'application/text' });
819-
res.end('Error updating document: Document ID is incorrect');
837+
res.end('Error updating document: Syntax error');
820838
}else{
821839
res.writeHead(200, { 'Content-Type': 'application/text' });
822840
res.end('Document successfully updated');
@@ -937,7 +955,6 @@ router.post('/drop_config', function (req, res, next) {
937955
});
938956

939957
// pagination API
940-
//router.get('/api/:conn/:db/:coll/:page/:search_key?/:search_value?', function (req, res, next) {
941958
router.post('/api/:conn/:db/:coll/:page', function (req, res, next) {
942959
var mongojs = require('mongojs');
943960
var connection_list = req.nconf.get('connections');

0 commit comments

Comments
 (0)