Running server side helping scripts - meteor

I am working on a multi-player card game (think Yu-gi-oh) based on real-world data. I have a collection "data" with data on individual items and another collection "cards" with actual issued cards in the game.
Cards have many-to-1 relationship with data-items (so one data-item is used to fill parameter-data for a number of copies of a single card, but with different owners).
"Cards" are published to client(s) as a local sub-set collection with all the "data"-items needed for all client-side "cards" items from client publication of "cards".
During the game, and especially in test phase, I need to "produce batches of cards" (and perhaps perform other setup and fine-tuning functions) from command-line/terminal/shell using helper functions with parameters (like cards.issue(10) that would create 10 new cards).
I would like to do that from command-line/terminal/shell to avoid writing admin front-end until I am sure about what will be done manually, and what automatically.
Where would I put a .js file with such helping scripts (functions with parameters) and how would I run them from terminal? How can I access meteor (server-side) objects from terminal/shell?

The easiest way to achieve this is a script in node.js.
1) You put those files whenever you want, just make sure they're not in the Meteor's scope of interest. So if you want to put them in your project directory, put them in a hidden (starting with a .) subfolder.
2) You run those files as typical node script: node path/to/file.js.
3) You don't need to access Meteor structure from that script, just the database. To do so, you need a Mongo driver (node mongodb package - here's the handy documentation), then:
Load it:
var MongoClient = require('mongodb').MongoClient;
Connect to the local db:
MongoClient.connect('local_db_url', function(err, db) {
...
});
Inside the connect callback, insert your objects:
var cards = db.collection('cards');
cards.insert(card, {safe: true});

Related

How to set a field for every document in a Cosmos db?

What would a Cosmos stored procedure look like that would set the PumperID field for every record to a default value?
We are needing to do this to repair some data, so the procedure would visit every record that has a PumperID field (not all docs have this), and set it to a default value.
Assuming a one-time data maintenance task, arguably the simplest solution is to create a single purpose .NET Core console app and use the SDK to query for the items that require changes, and perform the updates. I've used this approach to rename properties, for example. This works for any Cosmos database and doesn't require deploying any stored procs or otherwise.
Ideally, it is designed to be idempotent so it can be run multiple times if several passes are required to catch new data coming in. If the item count is large, one could optionally use the SDK operations to scale up throughput on start and scale back down when finished. For performance run it close to the endpoint on an Azure Virtual Machine or Function.
For scenarios where you want to iterate through every item in a container and update a property, the best means to accomplish this is to use the Change Feed Processor and run the operation in an Azure function or VM. See Change Feed Processor to learn more and examples to start with.
With Change Feed you will want to start it to read from the beginning of the container. To do this see Reading Change Feed from the beginning.
Then within your delegate you will read each item off the change feed, check it's value and then call ReplaceItemAsync() to write back if it needed to be updated.
static async Task HandleChangesAsync(IReadOnlyCollection<MyType> changes, CancellationToken cancellationToken)
{
Console.WriteLine("Started handling changes...");
foreach (MyType item in changes)
{
if(item.PumperID == null)
{
item.PumperID = "some value"
//call ReplaceItemAsync(), etc.
}
}
Console.WriteLine("Finished handling changes.");
}

Meteor restrict access to files and collections

In my app I have an admin area, this area also has some admin-client MiniMongo that goes with it. So the admin is subscribing to a publication, then this publication puts data from several collections on this admin-client MiniMongo collection.
The access to the subscription is restricted, and so is the publication. But I noticed that the collection still registers for every user, though (I believe) it is being populated with data only when an admin logs in.
Still I would like to know if I can create the collection only when an admin logs in and not before?
Now the code is being run on app startup.
Right now I have it like this:
export const AdminData = new Mongo.Collection("admin_data");
I also tried this, but it doesn't work:
let AdminData;
if(Roles.userIsInRole(Meteor.userId(), 'admin'){
AdminData = new Mongo.Collection("admin_data");
}
export default AdminData;
If it make a difference this is my project structure
client/
server/
imports/
--api/
----admin/
------client/
--------adminData.js (the above file)
------server/
--------publication.js
--startup/
----client/
----both/
----server/
--ui/
----adminArea/
------adminComponent.jsx (the collection is only imported here)
You should create the collection on the server. (move adminData.js besides publication.js). Also you should check security in the publication itself. Meteor.userId() will only work in meteor methods on the server and you will have an error if you leave it there
On the client your adminComponent needs to subscribe to the server publication to have access to the collection's data. To do so you'll need to wrap your component in something like react-komposer
Hope this helps

Meteor.JS: Subscribe no working

Im trying to subscribe my client side to my userFriends collection and Chrome's console display: userFriends is not defined
This is my code:
Server side...
userFriends = new Mongo.Collection("friends");
console.log(userFriends.find().fetch())
Meteor.publish("friends", function () {
return userFriends.find();
});
NOTE: The console.log display in the terminal an empty array which is good
Client side...
Meteor.subscribe("friends");
console.log(userFriends.find().fetch())
NOTE: This is where Chrome's console display the error
what am I doing wrong ?
Thank you
UPDATE 1: Now I can see the Friends collection in Chrome's console, but i cant insert data. I have the subscribe in client.js inside my client folder and my insert code is in friend.js inside client folder aswell.
The collection needs to be defined on both the client and the server. Typically this is done by placing the definition in a shared directory like lib:
lib/collections/user-friends.js
userFriends = new Mongo.Collection('friends');
Note the convention is to name the collection with the capitalized camel case version of the collection name. So calling it Friends would be more typical.
You need to declare the collection on both environments using shared code.
lib/user-friends.js
userFriends = new Mongo.Collection("friends");
client/user-friends.js
Meteor.subscribe("friends", function(){
console.log(userFriends.find().fetch());
});
In the client, be aware that collection subscriptions are asynchronous by nature (there's network latency on the client, inherent to fetching the documents from the server).
This is why if you console.log your collection content right after Meteor.subscribeing you'll get [], but if you wait until the subscription is ready using a callback, documents will be displayed correctly.
You have two correct answers but they do assume some knowledge for you. Here's what it looks like using Meteor's file structure (available at http://docs.meteor.com/#/full/structuringyourapp).
In your /lib (shared) directory
Make a file called "collections.js" and in it create your collection.
userFriends = new Mongo.Collection("friends");
I would instead do userFriends = new Mongo.Collection("userfriends"); so that your are always using the same word for your collection and you change the capitalization depending on if you're working on client or server. This is very helpful.
In Your /client directory
Make a file called "subscriptions.js" and in it subscribe to your collection.
Meteor.subscribe('friends');
In Your /server directory
Make a file called "publications.js" and in it publish your collection.
Meteor.publish('friends',function(){
return userFriends.find();
});
You don't need a fetch or anything there.
Essentially your code is failing because of where you're trying to house everything. What I've given you is three points of where you work. Client, Shared, Server. Set your app up that way and it will be easy to immediately figure out where you're working.
Hope that helps.

Clone or edit local Minimongo collection

I'd like to clone a minimongo collection so I can do some calculations, get a result, then push those results back to the server.
Assuming this is a suitable pattern, how best can I clone a minimongo collection?
It appears that in the object no longer has a ._deepcopy (1.0.4), and attempting an EJSON.clone exceeds the callstack size for even tiny collections. Underscore's _.clone() only copies by reference.
Alternatively, I could just edit the local collection via collection._collection.update. But if that's the case, what would happen if on the off chance the server updated or removed a doc while it was processing? I watched this video, but am still unclear on that scenario: https://www.eventedmind.com/feed/meteor-how-does-the-client-synchronize-writes-with-the-server
The why behind your pattern escapes me but one solution could be to define a null collection, (docs) copy the records you need to that, do your work, and then copy back the results into the original collection for automatic sync back to the server.
myLocalCollection = new Mongo.Collection(null);

Is there a way to tell meteor a collection is static (will never change)?

On my meteor project users can post events and they have to choose (via an autocomplete) in which city it will take place. I have a full list of french cities and it will never be updated.
I want to use a collection and publish-subscribes based on the input of the autocomplete because I don't want the client to download the full database (5MB). Is there a way, for performance, to tell meteor that this collection is "static"? Or does it make no difference?
Could anyone suggest a different approach?
When you "want to tell the server that a collection is static", I am aware of two potential optimizations:
Don't observe the database using a live query because the data will never change
Don't store the results of this query in the merge box because it doesn't need to be tracked and compared with other data (saving memory and CPU)
(1) is something you can do rather easily by constructing your own publish cursor. However, if any client is observing the same query, I believe Meteor will (at least in the future) optimize for that so it's still just one live query for any number of clients. As for (2), I am not aware of any straightforward way to do this because it could potentially mess up the data merging over multiple publications and subscriptions.
To avoid using a live query, you can manually add data to the publish function instead of returning a cursor, which causes the .observe() function to be called to hook up data to the subscription. Here's a simple example:
Meteor.publish(function() {
var sub = this;
var args = {}; // what you're find()ing
Foo.find(args).forEach(function(document) {
sub.added("client_collection_name", document._id, document);
});
sub.ready();
});
This will cause the data to be added to client_collection_name on the client side, which could have the same name as the collection referenced by Foo, or something different. Be aware that you can do many other things with publications (also, see the link above.)
UPDATE: To resolve issues from (2), which can be potentially very problematic depending on the size of the collection, it's necessary to bypass Meteor altogether. See https://stackoverflow.com/a/21835534/586086 for one way to do it. Another way is to just return the collection fetch()ed as a method call, although this doesn't have the benefits of compression.
From Meteor doc :
"Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass {reactive: false} as an option to find."
I think this simple option is the best answer
You don't need to publish your whole collection.
1.Show autocomplete options only after user has inputted first 3 letters - this will narrow your search significantly.
2.Provide no more than 5-10 cities as options - this will keep your recordset really small - thus no need to push 5mb of data to each user.
Your publication should look like this:
Meteor.publish('pub-name', function(userInput){
var firstLetters = new RegExp('^' + userInput);
return Cities.find({name:firstLetters},{limit:10,sort:{name:1}});
});

Resources