├── Mongol.gif
├── README.md
├── client
└── README.md
├── package.js
└── server
├── methods.js
└── utilities.js
/Mongol.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msavin/Mongol-meteor-explore-minimongo-devtools/9f53390a39527107c19c5ba8859175a13e432771/Mongol.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Meet Mongol, the Original Development Tool for Meteor
2 | =====================================================
3 |
4 | Starting today, you'll never have to enter the console to play with your collections again. With Mongol, you can view and edit your client documents right in the browser. And because Mongol is a debugOnly package, it does not compile to your production build.
5 |
6 |
7 |
8 | Plug & Play Installation
9 | ------------------------
10 |
11 | Mongol configures automatically. To get started, run:
12 |
13 | meteor add msavin:mongol
14 |
15 | After installation, open your app in the browser and press Control + M to toggle it.
16 |
17 | Note: the new Mongol requires Meteor 1.8
18 |
19 | How Does It Work?
20 | -----------------
21 | Using Mongol is like having a database management tool with-in your Meteor application. Meteor publishes data to `minimongo`, and Mongol hooks into it to show you all the documents on the client. With that, you can:
22 | - View the document counts in your `minimongo` collection
23 | - Browse the documents in your `minimongo` collection
24 | - Modify any document as if you have insecure on
25 | - Get a reactive window into your data
26 |
27 | FAQ
28 | ---
29 | Does Mongol require insecure to work? No, Mongol has special set of method's that allow it to interact with the database without disrupting your application permissions.
30 |
31 | Is there a security risk to using Mongol? Since Mongol is a `debugOnly` package, Meteor's build process will not compile it into production code.
32 |
33 | Will Mongol cause my application to behave differently? All of the code and functions of Mongol are pre-fixed and scoped, so there shouldn't be any intrusion.
34 |
35 | About
36 | -----
37 |
38 |
Mongol is part of Meteor Toys,
39 | and is licensed under the Meteor Toys License.
40 | (C) Toys, Inc 2019. All Rights Reserved.
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | As of over 6 months ago, the Mongol UI has become part of the proprietary Meteor Toys package set. However, the free edition is still available and has only been getting better. Additionally, the Methods that power Mongol are still available as open source.
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'msavin:mongol',
3 | summary: 'In-App MongoDB Editor',
4 | version: '2.0.0',
5 | git: 'https://github.com/msavin/Mongol.git',
6 | documentation: 'README.md',
7 | debugOnly: true
8 | });
9 |
10 | Package.onUse(function(api) {
11 |
12 | var serverFiles = [
13 | "server/methods.js",
14 | "server/utilities.js"
15 | ];
16 |
17 | api.versionsFrom("1.0");
18 |
19 | // This must go before: api.use('dburles:mongo-collection-instances@0.3.1');
20 | // Weak dependency: only used if app contains package
21 | api.use('aldeed:collection2@3.0.0', {weak: true});
22 |
23 | api.use('dburles:mongo-collection-instances@0.3.4');
24 | api.use(["underscore","check"], "server")
25 |
26 | api.add_files(serverFiles, "server");
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/server/methods.js:
--------------------------------------------------------------------------------
1 | Meteor.methods({
2 | Mongol_update: function (collectionName, documentData, originalDocumentData) {
3 |
4 | check(collectionName, String);
5 | check(documentData, Object);
6 | check(originalDocumentData, Object);
7 |
8 | var MongolCollection = Mongol.Collection(collectionName),
9 | documentID = documentData._id,
10 | originalID = originalDocumentData._id;
11 |
12 | var currentDbDoc = MongolCollection.findOne({
13 | _id: documentID
14 | }, {
15 | transform: null
16 | });
17 |
18 | if (!currentDbDoc) {
19 | // A document with this _id value is not in the db
20 | // Do an insert instead
21 | Meteor.call("Mongol_insert", collectionName, documentData);
22 | return;
23 | }
24 |
25 | delete documentData._id;
26 | delete originalDocumentData._id;
27 | delete currentDbDoc._id;
28 |
29 | var updatedDocumentData = Mongol.diffDocumentData(currentDbDoc, documentData, originalDocumentData);
30 |
31 |
32 | // Check for packages
33 |
34 | if (!!Package['aldeed:collection2'] && _.isFunction(MongolCollection.simpleSchema) && MongolCollection._c2) {
35 |
36 | // This is to nullify the effects of SimpleSchema/Collection2
37 | // Using `upsert` means that a user can change the _id value in the JSON
38 | // and then press the 'Update' button to create a duplicate (published keys/values only) with a different _id
39 |
40 | MongolCollection.update({
41 | _id: documentID
42 | }, {$set: updatedDocumentData}, {
43 | bypassCollection2: true
44 | });
45 |
46 | return;
47 | }
48 |
49 | // Run the magic
50 | MongolCollection.update({
51 | _id: documentID
52 | },
53 | updatedDocumentData
54 | );
55 |
56 | },
57 | Mongol_remove: function (collectionName, documentID, doNotTrash) {
58 |
59 | check(collectionName, String);
60 | check(documentID, String);
61 | check(doNotTrash, Match.Any);
62 |
63 |
64 | var MongolCollection = Mongol.Collection(collectionName);
65 | var docToBeRemoved = MongolCollection.findOne(documentID, {transform: null});
66 |
67 |
68 | // Start Trash Can
69 | if(typeof doNotTrash === 'undefined') {
70 |
71 | targetCollection = Mongol.Collection("MeteorToys.Mongol");
72 | trashDocument = docToBeRemoved;
73 | trashDocument["Mongol_origin"] = String(collectionName);
74 | trashDocument["Mongol_date"] = new Date();
75 | targetCollection.insert(trashDocument);
76 |
77 | }
78 | // End Trash Can
79 |
80 | // remove the document
81 | MongolCollection.remove(documentID);
82 |
83 | return docToBeRemoved;
84 |
85 | },
86 | Mongol_duplicate: function (collectionName, documentID) {
87 |
88 | check(collectionName, String);
89 | check(documentID, String);
90 |
91 | var MongolCollection = Mongol.Collection(collectionName),
92 | OriginalDoc = MongolCollection.findOne(documentID, {transform: null});
93 |
94 | if (OriginalDoc) {
95 |
96 | delete OriginalDoc._id;
97 |
98 | // Convert date strings to Date()
99 | var revisedDocument = OriginalDoc;
100 |
101 | var NewDocumentId = Mongol.insertDoc(MongolCollection, revisedDocument);
102 |
103 | return NewDocumentId;
104 |
105 | }
106 |
107 | },
108 | Mongol_insert: function(collectionName, documentData) {
109 |
110 | check(collectionName, String);
111 | check(documentData, Object);
112 |
113 | var MongolCollection = Mongol.Collection(collectionName),
114 | newId = null;
115 |
116 | if (documentData._id && MongolCollection.findOne({_id: documentData._id}, {transform: null})) {
117 | console.log('Duplicate _id found');
118 | return null;
119 | }
120 |
121 | revisedDocument = documentData;
122 |
123 |
124 | // Insert it
125 |
126 | var newId = Mongol.insertDoc(MongolCollection, revisedDocument);
127 |
128 | return newId;
129 |
130 | }
131 | });
132 |
--------------------------------------------------------------------------------
/server/utilities.js:
--------------------------------------------------------------------------------
1 | Mongol = {};
2 |
3 | Mongol.Collection = function (collectionName) {
4 |
5 | // Go through a variety of means of trying to return the correct collection
6 | return Mongo.Collection.get(collectionName)
7 | // This should automatically match all collections by default
8 | // including namespaced collections
9 |
10 | || ((Meteor.isServer) ? eval(collectionName) : Meteor._get.apply(null,[window].concat(collectionName.split('.'))))
11 | // For user defined collection names
12 | // in the form of Meteor's Mongo.Collection names as strings
13 |
14 | || ((Meteor.isServer) ? eval(firstToUpper(collectionName)) : Meteor._get.apply(null,[window].concat(firstToUpper(collectionName).split('.'))))
15 | // For user defined collections where the user has typical upper-case collection names
16 | // but they've put actual mongodb collection names into the Mongol config instead of Meteor's Mongo.Collection names as strings
17 |
18 | || null;
19 | // If the user has gone for unconventional casing of collection names,
20 | // they'll have to get them right (i.e. Meteor's Mongo.Collection names as string) in the Mongol config manually
21 |
22 | // Changes the first character of a string to upper case
23 |
24 | function firstToUpper(text) {
25 |
26 | return text.charAt(0).toUpperCase() + text.substr(1);
27 |
28 | }
29 | }
30 |
31 | Mongol.insertDoc = function (MongolCollection, documentData) {
32 |
33 | check(MongolCollection, Match.Any);
34 | check(documentData, Match.Any);
35 |
36 | if (!!Package['aldeed:collection2'] && _.isFunction(MongolCollection.simpleSchema) && MongolCollection._c2) {
37 | // This is to nullify the effects of SimpleSchema/Collection2
38 | newId = MongolCollection.insert(documentData, {
39 | bypassCollection2: true
40 | });
41 | }
42 | else {
43 | newId = MongolCollection.insert(documentData);
44 | }
45 | return newId;
46 | }
47 |
48 | // This function takes three data points into account:
49 |
50 | // 1) the actual document as it stands on the server, prior to being updated
51 | // 2) the oldData that was on the client before the user pressed save
52 | // 3) the newData that the client is trying to save
53 |
54 | // This function decides which fields it is going to make writes to on this basis:
55 | // 1) The field(s) being overwritten must appear in the db doc and on the client oldData
56 | //(if they only appear in the oldData these must have been added dynamically on the client
57 | // and we don't want to save these fields to the db)
58 | //-- this includes fields that are being removed (i.e. they must appear in the db doc and the oldData)
59 | // 2) Only fields that appear in the newData, but not the oldData or db doc can be added
60 | //(if it appears in the db doc, throw an error that says:
61 | // "There is an unpublished field in the database with that name. Update cannot be made.")
62 |
63 | // The ramifications of all this:
64 | // You can only update/remove fields that are published
65 | // You can only add new fields if they don't exist in the db already
66 |
67 |
68 | Mongol.diffDocumentData = function (dbDoc, newData, oldData) {
69 |
70 | var finalData = {};
71 |
72 | var dbDocFields = _.keys(dbDoc);
73 | var newDataFields = _.keys(newData);
74 | var oldDataFields = _.keys(oldData); // console.log("dbDocFields",dbDocFields); console.log("newDataFields",newDataFields); console.log("oldDataFields",oldDataFields);
75 |
76 | // First get the set of fields that we won't be saving because they were dynamically added on the client
77 |
78 | var dynamicallyAddedFields = _.difference(oldDataFields, dbDocFields);
79 |
80 | // Then get the fields that must retain their dbDoc field value, because they we'ren't published
81 |
82 | var unpublishedFields = _.difference(dbDocFields, oldDataFields); // console.log("unpublishedFields",unpublishedFields);
83 |
84 | // iterate over all fields, old and new, and ascertain the field value that must be added to the final data object
85 |
86 | var oldAndNewFields = _.union(dbDocFields, newDataFields);
87 |
88 | _.each(oldAndNewFields, function(field) {
89 |
90 | if (_.contains(dynamicallyAddedFields, field)) {
91 |
92 | // We don't want to add this field to the actual mongodb document
93 | console.log("'" + field + "' appears to be a dynamically added field. This field was not updated.");
94 | return;
95 |
96 | }
97 |
98 | if (_.contains(unpublishedFields, field)) {
99 |
100 | // We don't want to overwrite the existing mondodb document value
101 | if (newData[field]) {
102 | // Give a message to user as to why that field wasn't updated
103 | console.log("'" + field + "' is an unpublished field. This field's value was not overwritten.");
104 | }
105 | // Make sure the old value is retained
106 | finalData[field] = dbDoc[field];
107 | return;
108 |
109 | }
110 |
111 | if (!_.isUndefined(newData[field])) {
112 |
113 | finalData[field] = (_.isObject(newData[field]) && !_.isArray(newData[field]) && !_.isDate(newData[field])) ? Mongol.diffDocumentData(dbDoc[field] || {}, newData[field], oldData[field] || {}) : newData[field];
114 |
115 | }
116 |
117 | });
118 |
119 | return finalData;
120 |
121 | };
122 |
--------------------------------------------------------------------------------