Meteor CollectionFS Collection is Undefined? - javascript

I am trying to use CollectionFS and GridFS to upload some images to my app and serve them back.
I have the following definitions:
ImageStore.js:
var imageStore = new FS.Store.GridFS("images", {
mongoUrl: 'mongodb://127.0.0.1:27017/test/',
transformWrite: myTransformWriteFunction,
transformRead: myTransformReadFunction,
maxTries: 1,
chunkSize: 1024*1024
});
EventImages = new FS.Collection("images", {
stores: [imageStore]
});
ImageStorePub.js:
Meteor.publish("EventImages", function() {
return EventImages.find();
});
ImageUploadHandler.js:
if (Meteor.isServer) {
EventImages.allow({
'insert': function() {
// add custom authentication code here
return true;
}
});
}
After typing all of this I tried wrapping them all in a if(Meteor.isServer){...} despite the fact that they're already in my server folder, but my app is still crashing due to error ReferenceError: EventImages is not defined
at server/route handlers/ImageUploadHandler.js:2:1

I made a mistake in not assigning the variable on both the client and server.

Related

Meteor Files Storing a image url in Mongo collection

I'm really lost when it comes to file uploading in meteor and manage the data between client and server.
I'm using Meteor Files from Veliov Group to upload multiple images on the client side. They're getting stored in a FilesCollection called Images and I have my Mongo.Collection called Adverts.
collections.js:
Adverts = new Mongo.Collection('adverts');
Images = new FilesCollection({
collectionName: 'Images',
storagePath: () => {
return `~/public/uploads/`;
},
allowClientCode: true, // Required to let you remove uploaded file
onBeforeUpload(file) {
// Allow upload files under 10MB, and only in png/jpg/jpeg formats
if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.ext)) {
return true;
} else {
return 'Limit 10mb';
}
}
});
// if client subscribe images
if (Meteor.isClient) {
Meteor.subscribe('files.images.all');
};
// if server publish images
if (Meteor.isServer) {
Images.allowClient();
Meteor.publish('files.images.all', () => {
return Images.collection.find();
});
};
What I'm trying to achieve is, when I upload the images, I wanna store the URLs on the document in Adverts that I'm working with (I'm using iron:router to access those documents _id).
I managed to get the URL but only for the first image uploaded, my code for what I saw on the docs:
Template.imageUpload.helpers({
imageFile: function () {
return Images.collection.findOne();
},
myImage: () => {
console.log(Images.findOne({}).link())
}
})
Template.imageUpload.events({
'change #fileInput': function (e, template) {
if (e.currentTarget.files) {
_.each(e.currentTarget.files, function (file) {
Images.insert({
file: file
});
});
}
}
})
I was using a Meteor.Call to send the URL to the server, but I couldn't manage to update the document with a new property pic and the value url of the image
server.js:
imageUpload: (actDoc, imgURL) => { // actDoc is the document id that I'm working on the client
Adverts.update({'reference': actDoc}, {$set: {'pic': imgURL}})
},
This is probably a dumb question and everything might in the docs, but I've readed those docs back and forth and I can't manage to understand what I need to do.
The answer for my problem was to do it server side
main.js server
FSCollection.on('afterUpload'), function (fileRef) {
var url = 'http://localhost:3000/cdn/storage/images/' + fileRef._id + '/original/' + fileRef._id + fileRef.extensionWithDot;
}
MongoCollection.update({'_id': docId}, { $set: {url: imgUrl }}})

Value not set to global variable in JS/AngularJs

I am using gulp to run and build to run my application. I am getting file contents using $http service in my index.js file and then setting value of a variable like
window.variablex = "http://localhost:8080/appname".
here is how I am doing it (in index.js)
(function ()
{
'use strict';
angular
.module('main')
.controller('IndexController', IndexController);
function IndexController($http){
$http.get('conf/conf.json').success(function(data){
window.variable = data.urlValue;
}).error(function(error){
console.log(error);
});
}
});
And I've created a factory to call the rest APIs of my backend application like
(function(){
'use strict';
angular
.module('main')
.factory('testService',['$resource',testService]);
function agentService($resource){
var agents = $resource('../controller/',{id:'#id'},
{
getList:{
method:'GET',
url:window.variable+"/controller/index/",
isArray:false
}
});
Now, I except a rest call to made like
http://localhost:8080/appname/controller
But it always sends a call like http://undefined/appname/controller which is not correct.
I can get the new set value anywhere else, but this value is not being set in resource service objects somehow.
I am definitely missing something.
Any help would be much appreciated
As you are using Gulp, I advise you to use gulp-ng-config
For example, you have your config.json:
{
"local": {
"EnvironmentConfig": {
"api": "http://localhost/"
}
},
"production": {
"EnvironmentConfig": {
"api": "https://api.production.com/"
}
}
}
Then, the usage in gulpfile is:
gulp.task('config', function () {
gulp.src('config.json')
.pipe(gulpNgConfig('main.config', {
environment: 'production'
}))
.pipe(gulp.dest('.'))
});
You will have this output:
angular.module('myApp.config', [])
.constant('EnvironmentConfig', {"api": "https://api.production.com/"});
And then, you have to add that module in your app.js
angular.module('main', [ 'main.config' ]);
To use that variable you have to inject in your provider:
angular
.module('main')
.factory('testService', ['$resource', 'EnvironmentConfig', testService]);
function agentService($resource, EnvironmentConfig) {
var agents = $resource('../controller/', {id: '#id'},
{
getList: {
method: 'GET',
url: EnvironmentConfig + "/controller/index/",
isArray: false
}
});
}
#Kenji Mukai's answer did work but I may have to change configuration at run time and there it fails. This is how I achieved it (in case anyone having an issue setting variables before application gets boostrap)
These are the sets that I followed
Remove ng-app="appName" from your html file as this is what causing problem. Angular hits this tag and bootstraps your application before anything else. hence application is bootstratped before loading data from server-side (in my case)
Added the following in my main module
var injector = angular.injector(["ng"]);
var http = injector.get("$http");
return http.get("conf/conf.json").then(function(response){
window.appBaseUrl = response.data.gatewayUrl
}).then(function bootstrapApplication() {
angular.element(document).ready(function() {
angular.bootstrap(document, ["yourModuleName"]);
});
});
This will load/set new values everytime you refresh your page. You can change conf.json file even at runtime and refreshing the page will take care of updating the values.

Meteor: Not able to upload image to S3 using CollectionFS

I am trying to test the upload functionality using this guide with the only exception of using cfs-s3 package. This is very basic with simple code but I am getting an error on the client console - Error: Access denied. No allow validators set on restricted collection for method 'insert'. [403]
I get this error even though I have set the allow insert in every possible way.
Here is my client code:
// client/images.js
var imageStore = new FS.Store.S3("images");
Images = new FS.Collection("images", {
stores: [imageStore],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Images.deny({
insert: function(){
return false;
},
update: function(){
return false;
},
remove: function(){
return false;
},
download: function(){
return false;
}
});
Images.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
},
download: function(){
return true;
}
});
And there is a simple file input button on the homepage -
// client/home.js
'change .myFileInput': function(e, t) {
FS.Utility.eachFile(e, function(file) {
Images.insert(file, function (err, fileObj) {
if (err){
console.log(err) // --- THIS is the error
} else {
// handle success depending what you need to do
console.log("fileObj id: " + fileObj._id)
//Meteor.users.update(userId, {$set: imagesURL});
}
});
});
}
I have set the proper policies and everything on S3 but I don't think this error is related to S3 at all.
// server/images.js
var imageStore = new FS.Store.S3("images", {
accessKeyId: "xxxx",
secretAccessKey: "xxxx",
bucket: "www.mybucket.com"
});
Images = new FS.Collection("images", {
stores: [imageStore],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
I have also published and subscribed to the collections appropriately. I have been digging around for hours but can't seem to figure out what is happening.
EDIT: I just readded insecure package and everything now works. So basically, the problem is with allow/deny rules but I am actually doing it. I am not sure why it is not acknowledging the rules.
You need to define the FS.Collection's allow/deny rules in sever-only code. These are server-side rules applied to the underlying Mongo.Collection that FS.Collection creates.
The best approach is to export the AWS keys as the following environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, remove the accessKeyId and secretAccessKey options from the FS.Store, and then move the FS.Collection constructor calls to run on both the client and server. The convenience of using env vars is mentioned on the cfs:s3 page
In addition to this you can control the bucket name using Meteor.settings.public, which is handy when you want to use different buckets based on the environment.

CollectionsFS File is not uploaded to server

i am working myself through the discover meteor project (microscope) and tried to add a file upload, which i wanted to do by CollectionFS. My microscope implementation is quite minimal. I am trying to rebuild a minimal dribbble or Workdesk show and tell website.
I installed:
cfs:standard-packages
cfs:filesystem
cfs:ui
Next I am having a collection called rooms which stores a room with a name, for a user (lib/collections/rooms.js):
Rooms = new Mongo.Collection("rooms");
And a roomImages CollectionFS Collection (lib/collections/roomImages.js):
var imageStore = new FS.Store.FileSystem("roomImageStore", {
path: "upload",
maxTries: 5 //optional, default 5
});
RoomFS = new FS.Collection('roomImages', {
stores: [imageStore],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
RoomFS.allow({
insert: function () {
return true;
},
update: function () {
return true;
},
remove: function () {
return true;
},
download: function () {
return true;
}
});
As I have removed referencing for reducing the debug effort I have this publications.js
Meteor.publish('rooms', function() {
return Rooms.find();
});
Meteor.publish('singleRoom', function(id) {
check(id, String);
return Rooms.find(id);
});
Meteor.publish('roomImages', function(){
return RoomFS.find();
});
Inserting a room works. After the room initially is created, the user then is routed to the rooms editing page.
<template name="roomEdit">
<form class="main form">
<input name="files" type="file" class="fileUploader" multiple>
{{#each images}}
{{#unless this.isUploaded}}
{{> FS.UploadProgressBar bootstrap=true}}
{{/unless}}
{{/each}}
</form>
</template>
I took the function off the documentation in the readme:
Template.roomEdit.events({
'change .fileUploader': function (event, template) {
FS.Utility.eachFile(event, function(file) {
RoomFS.insert(file, function (err, fileObj) {
//Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
});
});
});
Now in my collections there are
cfs._tempstore.chunks
cfs.roomImages.filerecord
after trying to upload one image (the progress bar is not showing) cfs.roomImages.filerecord has the file as collection item, but the uploads folder keeps being empty, therefore I think the file is not uploaded, also if I don't give a path, the default folder is not generated.
I have already read both documentations (website and github) and tried different examples, but most of them seem to be outdated.
Am I missing something? I have no real idea why the file is not uploaded to the server.
If you have the subscription on the client, try this code.
First on the /lib/collection.js folder declare the FSCollection like this
var imageStore = new FS.Store.FileSystem("roomImageStore", {
path: "upload",
maxTries: 5 //optional, default 5
});
roomImages = new FS.Collection('roomImages', {
stores: [imageStore]
});
And not the Same file subscribe to the FSCollection.
if(Meteor.isClient) {
Meteor.subscribe('RoomFS');
}
Now on the /server/collections.js make the same publish you have.
Meteor.publish('roomImages', function(){
return roomImages.find();
});
roomImages.allow({
insert:function(userId,doc){
if(Meteor.userId()){
return true; //if user is logged we return true
} else{
console.log("some foreign user try to upload a file take care"); //server log
return false
}
}
})
we create and subscribe the FSCollection on the /lib folder.. why? because the lib folder its the firs thing meteor loads, so with that we have the fsCollection available on both server/client.
Now we need to upload a new file, so lets create a example template
First we don't want the file to load when we click "accept" on the file input so lets put a submit file button, so the html looks like this.
on Client/exampleUpload.html
<template name="example">
<div class="form-group">
<label>Upload the Image</label>
<input id="testImage" type="file">
</div>
<button type="submit" id="uploadTest"> Click to upload</button>
</template>
on Client/exampleUpload.js
//events
Template.example.events({
'click #uploadTest':function(){
var file $('#testImage').get(0).files[0] / here we store the file
var fsFile = new fsFile(file); // here we add to the fsFile instance
fsFile.metadata = {
coolTextToImage:"this is a cool text" // here we add some metadata to the fs file
}
if(file === undefined){
alert("IF NOT IMAGE NOT INSER") //here we add some validation
} else{
roomImages.insert(fsFile,function(err,result){
if(err){
console.log(err.reason) // here we check if some error occurs when inserting
} else{
console.log(result) // if everything ok, wee should see a console.log with some like [Fs.file] object
}
})
}
}
})
Edit
I recommend you to use gridFS,check at this gitHub issue and also if you use FSfileSystem on production on each deploy the files will be deleted(i think Modulus.io respect the Path).
How to fix it? use the other 2 adapter gridFs or s3, in my case i use GridFS, and GraphicsMagic Package
So first Install the GM package
meteor add cfs:graphicsmagick
With this package you can control the size, type, etc of the file(image)
And declare the new FsCollection like this
imageStore = new FS.Collection("imageStores", {
stores: [new FS.Store.GridFS("imageStore",{
beforeWrite:function(fileObj){
return {
extension: 'png',
type: 'image/png'
};
},
transformWrite:function(fileObj, readStream, writeStream){
// Aqui la convierte en una imagen segun de 10x10 seguuuun
gm(readStream).resize(400).stream('PNG').pipe(writeStream); //resize depends your needs
}
})]
});
this is just a recommendation if you are planning deploy the app
Tell me if works, GL

How to create a normal sails model without being in the models folder

So,
I'm in the middle of implementing a plugin api for my application, and the plugins can have their own models, imagine this.
SimplePlugin = {
pluginName: 'simple',
pluginConfig: {},
SimpleModel: {
attributes: {
name: 'string'
}
}
}
So I need to be able to create the "one-time" model with a function whenever it's needed, it needs to have exactly the same functionality as other models so you automatically get the urls like /simplePlugin/:id for find ..etc
Thanks
What are you trying to do is not easy and a bit messy with Sails in the current state of the project. I'm referring to the v0.10 version. What you'll have to do is
Inject the model definition found in SimplePlugin.SimpleModel into sails.models
Inject a dummy controller for the model with _config: { rest: true }
Please note that the code examples I posted are taken from a custom Sails hook I am working on and assume access to sails and the code examples to be executed during the loadHooks phase of Sails initialization / before the MiddlewareRegistry phase (compare: lib/app/load.js).
1. Inject model definition
Following the hints in the orm hook in Sails v0.10 you have to:
Get the models and adapters defined in api, merge your new model into the dictionary
Normalize the model definitions via sails.hooks.orm.normalizeModelDef
Load the normalized model definitions into Waterline
Unload exisisting adapter connections via teardown
Reinitialize Waterline
Expose the initialized Waterline collections to sails and the global scope via sails.hooks.orm.prepareModels (previously: sails.hooks.orm.exposeModels, changed with: 8d96895662)
Because you have to reinitialize Waterline and reload all model definitions I'd recommend to collect all model definitions to inject and pass them to the inject function once. The example code below reflects this.
...
function injectPluginModels(pluginModels, cb) {
// copy sails/lib/hooks/orm/loadUserModules to make it accessible here
var loadUserModelsAndAdapters = require('./loadUserModules')(sails);
async.auto({
// 1. load api/models, api/adapters
_loadModules: loadUserModelsAndAdapters,
// 2. Merge additional models, 3. normalize model definitions
modelDefs: ['_loadModules', function(next){
_.each(additionModels, function(aditionModel) {
_.merge(sails.models, additionalModel);
});
_.each(sails.models, sails.hooks.orm.normalizeModelDef);
next(null, sails.models);
}],
// 4. Load models into waterline, 5. tear down connections, 6. reinitialize waterline
instantiatedCollections: ['modelDefs', function(next, stack){
var modelDefs = stack.modelDefs;
var waterline = new Waterline();
_.each(modelDefs, function(modelDef, modelID){
waterline.loadCollection(Waterline.Collection.extend(modelDef));
});
var connections = {};
_.each(sails.adapters, function(adapter, adapterKey) {
_.each(sails.config.connections, function(connection, connectionKey) {
if (adapterKey !== connection.adapter) return;
connections[connectionKey] = connection;
});
});
var toTearDown = [];
_.each(connections, function(connection, connectionKey) {
toTearDown.push({ adapter: connection.adapter, connection: connectionKey });
});
async.each(toTearDown, function(tear, callback) {
sails.adapters[tear.adapter].teardown(tear.connection, callback);
}, function(){
waterline.initialize({
adapters: sails.adapters,
connections: connections
}, next)
});
}],
// 7. Expose initialized models to global scope and sails
_prepareModels: ['instantiatedCollections', sails.hooks.orm.prepareModels]
}, cb);
};
...
Would allow you to:
// Read your plugins
...
var pluginModels = // Get all the plugin models
injectPluginModels(pluginModels, function(){
// Plugin models now available via global[pluginModel.globalId] and sails.models[pluginModel.identity]
});
2. Inject controller
For each model that should be exposed via blueprint methods you have to:
Create a controller definition with matching identity and enabled blueprints
Save controller to sails.controllers[controllerId]
Save controller to sails.hooks.controllers.middleware[controllerId]
The Sails MiddlewareRegistry will automatically pick up the controllers found in these objects.
function mountBlueprintsForModels(pluginModels) {
_.each(pluginModels, function(pluginModel){
var controller = _.cloneDeep(pluginModel);
controller._config = { rest: true };
var controllerId = pluginModel.identity;
if (!_.isObject(sails.controllers[controllerId])) {
sails.controllers[controllerId] = controller;
}
if (!_.isObject(sails.hooks.controllers.middleware[controllerId])) {
sails.hooks.controllers.middleware[controllerId] = controller;
}
});
}
3. In action
// E.g. in /api/hooks/plugins/index.js
/*
* Module dependencies
*/
var async = require('async'),
_ = require('lodash'),
waterline = require('waterline');
module.exports = function(sails) {
// injectPluginModels and mountBlueprintsForModels defined here
...
return {
initialize: function(cb) {
sails.after('hook:orm:loaded', function() {
yourNiftyPluginLoader(function(err, plugins) {
// assuming plugin.models holds array of models for this plugin
// customize for your use case
var pluginModels = _.pluck(plugins, 'models');
injectPluginModels(pluginModels, cb);
mountBlueprintsForModels(pluginModels);
});
});
}
}
}
EDIT: not working completely since collections are assigned to connections at initialization.
Seems that there is a better solution, with 3 lines of code and without disconnecting/reconnecting databases. I just studied the source code of Waterline (see https://github.com/balderdashy/waterline/blob/master/lib/waterline.js#L109). It's possible to do something like:
var Waterline = require('waterline');
// Other dependencies
var Schema = require('waterline-schema');
var CollectionLoader = require('waterline/lib/waterline/collection/loader');
var orm = new Waterline();
var config = {
// Setup Adapters
// Creates named adapters that have have been required
adapters: {
'default': 'mongo',
mongo: require('sails-mongo')
},
// Build Connections Config
// Setup connections using the named adapter configs
connections: {
'default': {
adapter: 'mongo',
url: 'mongodb://localhost:27017/sausage'
}
}
};
orm.initialize(config, function(err, data) {
if (err) {
throw err;
}
// ORM initialized, let's add another model dynamically
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'default',
attributes: {
first_name: 'string',
last_name: 'string'
}
});
orm.loadCollection(User);
var defaults = config.defaults || {};
// This is where the magic happens
var loader = new CollectionLoader(User, orm.connections, defaults);
var collection = loader.initialize(orm);
orm.collections[collection.identity.toLowerCase()] = collection;
// Done! You can now use orm.collections.user :-D
});
In v0.12 sails.hooks.orm.normalizeModelDef doesn't exists anymore. Also sails/lib/hooks/orm/loadUserModules went to the sails-hook-orm npm module and is not longer part of sails.
Try this:
"Load models, controllers, services, policies and config from specified directories and inject them into the main Sails app."
https://github.com/leeroybrun/sails-util-mvcsloader
surprised sails doesn't support this in 2018: I have continued the above package with a fork ( #eyn answer) with updates that work for sails v1.x.x.
https://github.com/emahuni/sails-util-micro-apps
I changed it to that coz I am changing a lot of thing in that package. Instead of loading just models and controllers i want to it to load whole apps, mini-apps for a micro service architecture in sails. This is such that you can make mini-apps that can be joined together to form one large app out of reusable apis code.

Categories

Resources