├── 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 | --------------------------------------------------------------------------------