Glavin001 / sails-multitenancy-example

Example application using Sails (& Waterline) Multitenancy support. (Unmaintained, proof-of-concept)
MIT License
14 stars 4 forks source link

Cannot read property 'joins' of undefined #3

Open chipgata opened 9 years ago

chipgata commented 9 years ago

Hi you!

I made the following on my code:

 Game
    .tenant(req.session.tenant)
    .find({}).populate('developer').exec(function findCB(err,found){
        res.view({
          title: "Game Management",
          data : found,
          errors    : req.flash('error')
        });
    });

I get errors:

error: Sending 500 ("Server Error") response: 
 TypeError: Cannot read property 'joins' of undefined
    at _createParentOperation (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:255:15)
    at _stageOperations (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:171:39)
    at _buildOperations (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:153:21)
    at new module.exports (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/operations.js:40:26)
    at /Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/basic.js:252:26
    at /Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/collection/index.js:85:24
    at createTenantCollection (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/tenant.js:85:20)
    at /Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/tenant.js:123:18
    at bound.module.exports.connections.someMongodbServer.configForTenant (/Users/chipgata/Projects/ui-data-tracking/config/connections.js:75:20)
    at bound.module.exports (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/tenant.js:68:23)
    at bound [as tenant] (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/lodash/dist/lodash.js:729:21)
    at bound.Collection._resolveCollection (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/collection/index.js:84:18)
    at bound [as _resolveCollection] (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/lodash/dist/lodash.js:729:21)
    at bound.module.exports.find (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/finders/basic.js:248:10)
    at bound [as find] (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/lodash/dist/lodash.js:729:21)
    at Deferred.exec (/Users/chipgata/Projects/ui-data-tracking/node_modules/sails/node_modules/waterline/lib/waterline/query/deferred.js:501:16) [TypeError: Cannot read property 'joins' of undefined]

Please tell me I'm wrong place

Glavin001 commented 9 years ago

Joins may not yet be supported. I can write tests and fix this. Please provide me with some more information I can work with, such as your Mofel schemas.

chipgata commented 9 years ago

Thank you for your reply! Here my Model Schema User Model

var User = {
  // Enforce model schema in the case of schemaless databases
  schema: true,

  attributes: {
    firstname  : { type: 'string', defaultsTo: ''},
    lastname  : { type: 'string', defaultsTo: ''},
    username  : { type: 'string', unique: true },
    email     : { type: 'email',  unique: true },
    passports : { collection: 'Passport', via: 'user' },
    supperadmin: { type:'boolean', defaultsTo: false},
    isroot: { type:'boolean', defaultsTo: false},
    group: { model: 'Group', defaultsTo: '' },
    apps: {
      collection: 'GameAssign',
      via: 'user'
    }
  },

};

module.exports = User;

Group Model:

/**
* Group.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {

    name : { type: 'string', unique: true, required: true },

    description : { type: 'string' },

    status : { type:'boolean', defaultsTo: false },

    users: {
      collection: 'user',
      via: 'group'
    }
  }
};

And User Controller

/**
 * UsersController
 *
 * @description :: Server-side logic for managing users
 * @help        :: See http://links.sailsjs.org/docs/controllers
 */

module.exports = {

  /**
   * `UsersController.list()`
   */
  list: function (req, res) {
      res.locals.layout = 'layout2';
      User.find({id:{ '!' : [req.user.id] }}).populate('group').exec(function findCB(err,found){
        res.view({
          title: "User management",
          data : found,
          errors    : req.flash('error')
        });
    });

  },

  /**
   * `UsersController.create()`
   */
  changepass: function (req, res) {

    res.locals.layout = 'layout2';
    var id = req.param('id');

    if (req.method.toUpperCase() == 'POST'){
        //var id = req.param('id');
          var password    = req.param('password');
          if (!password) {
            return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Password.Missing',locale: 'en'})});
          }
          User.findOne({ id : id }).exec(function(err, user) { 
            if(!user)
              return res.json({'status':'error', 'msg':'User not found'});

            Passport.update({user:user.id},{password:password}).exec(function afterwards(err,updated){
              if (err) {
                if (err.code === 'E_VALIDATION') {

                  return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Password.Invalid',locale: 'en'})});
                }
                else
                  return res.json({'status':'error', 'msg':'Error Unknown'});
              }
              return res.json({'status':'success', 'msg':sails.__({phrase:'Update password success',locale: 'en'})})
            });
          })
    }
    else
        User.findOne({id:id}).exec(function(err, user){
          res.render('user/changepass', {
                errors    : req.flash('error'),
                user:user,
          });
        });

  },

  /**
   * `UsersController.create()`
   */
  create: function (req, res) {
    res.locals.layout = 'layout2';

    if (req.method.toUpperCase() == 'POST'){
        var group = req.param('group');
        if (!group) {
            return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Group.Missing',locale: 'en'})});
        }
        passport.protocols.local.register(req, res, function (err, user) {
              if(err){
                var flashError = req.flash('error')[0];
                return res.json({'status':'error', 'msg':sails.__({phrase:flashError,locale: 'en'})});
              }
              else{
                var apps = req.param('apps');

                for (var i = 0, len = apps.length; i < len; i++) {
                  GameAssign.create({user:user.id, game:apps[i]}).exec(function createCB(err, ga){
                      if(err)
                        console.log(err);

                  });
                }
                return res.json({'status':'success', 'msg':sails.__({phrase:'Create success',locale: 'en'})})
              }

          });
    }
    else
        Group.find({}).exec(function findCB(err,found){
          Game.find({}).exec(function findCB(err,games){
            res.render('user/create', {
              groups: found,
              apps: games,
              errors    : req.flash('error')
            });
          });
        });

  },

  /**
   * `UsersController.update()`
   */
  update: function (req, res) {
    res.locals.layout = 'layout2';
    var id = req.param('id');

    if (req.method.toUpperCase() == 'POST'){
        var id = req.param('id');
          var email    = req.param('email')
            , sadmin = req.param('sadmin')
            , group = req.param('group')
            , firstname = req.param('firstname')
            , lastname = req.param('lastname')
            , apps = req.param('apps');

          sadmin!=undefined ? sadmin = true : sadmin = false;

          if (!email) {
            return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Email.Missing',locale: 'en'})});
          }

          if (!group) {
            return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Group.Missing',locale: 'en'})});
          }

          User.findOne({ id : id }).exec(function(err, user) {  
              if(user.email!=email){
                return res.json({'status':'error', 'msg':sails.__({phrase:'Error.Passport.Email.Exists',locale: 'en'})});
              };

              User.update({id:id},{lastname:lastname, firstname:firstname, email:email, supperadmin:sadmin, group:group}).exec(function afterwards(err,updated){
                if (err) {
                  if (err.code === 'E_VALIDATION') {
                    if (err.invalidAttributes.email) {
                      return res.json({'status':'error', 'msg':'Email vas invalid.'});
                    } else {
                      return res.json({'status':'error', 'msg':'Email was existed.'});
                    }
                  }
                  if(err.code === 'E_UNKNOWN')
                    return res.json({'status':'error', 'msg':'Error Unknown'});    
                }

                GameAssign.destroy({user:id}).exec(function deleteCB(err){
                  if(err)
                    return res.json({'status':'error', 'msg':'Can not delete game assign'});
                  if(apps)
                    for (var i = 0, len = apps.length; i < len; i++) {
                        GameAssign.create({user:id, game:apps[i]}).exec(function createCB(err, ga){
                            if(err)
                              console.log(err);

                        });
                    }
                });

                return res.json({'status':'success', 'msg':sails.__({phrase:'Update success',locale: 'en'})})

              });
          });
    }
    else
        Group.find({}).exec(function findCB(err,found){
          Game.find({}).exec(function findCB(err,games){
            User.findOne({id:id}).populate('group').populate('apps').exec(function(err, user){
              res.render('user/update', {
                    errors    : req.flash('error'),
                    user:user,
                    groups:found,
                    apps: games,
              });
            });
          });
        });
  },

  /**
   * `UsersController.delete()`
   */
  delete: function (req, res) {

    res.locals.layout = 'layout2';
    var id = req.param('id');

    User.findOne({id:id}).exec(function(err, user){
      if(err){
        return res.json({'status':'error', 'msg':'User not found'});
      } 

      Passport.destroy({user:user.id}).exec(function deleteCB(err){
          if(err)
            return res.json({'status':'error', 'msg':'Can not delete passport user'});

          User.destroy({id:id}).exec(function deleteCB(err){
              if(err)
                return res.json({'status':'error', 'msg':'Can not delete this user'});

              return res.json({'status':'success', 'msg':sails.__({phrase:'success',locale: 'en'})})
          });
      });

    });

  },

};

Thank you for your supprt

Glavin001 commented 9 years ago

Where is your Game model?

chipgata commented 9 years ago

Here, Game Model!

/**
* Game.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {

    name : { type: 'string', unique: true, required: true },

    backend_name : { type: 'string', unique: true, required: true },

    description : { type: 'string' },

    api_game : { type: 'string', required: true },

    developer: { model: 'developer', defaultsTo: '' },
  }
};

Sorry,

Glavin001 commented 9 years ago

This is weird. This actually shouldn't be an issue. That error originates from https://github.com/Glavin001/waterline/blob/master/lib/waterline/query/finders/operations.js#L239-L255 There is a check for hasJoin() before calling connection.joins and since the Tenant Connection should be a proper Connection object this should work fine.

Could you help me debug? Add a console.log(connectionName, connections); at here https://github.com/Glavin001/waterline/blob/master/lib/waterline/query/finders/operations.js#L248-L249 This will show us the Tenant Connection's name for join and also what Connections are available.

Also, if you'd like to push your code to https://github.com/chipgata/ui-data-tracking and document the specific actions/requests you are using, it may be easier so I can reproduce this in your own app for you and debug. Thanks.

chipgata commented 9 years ago

Thank you very much!

Here, i had upload my source code via

https://github.com/chipgata/ui-data-tracking

Now, you can clone it and debug

Thank you for your support!

chipgata commented 9 years ago

I perform debug and result :

console.log(connectionName, connections);
somemongodbserver<tenant:abc_data_tracking_com> { someMongodbServer: 
   { collections: [ 'user', 'group' ],
     children: [],
     joins: [ [Object] ] } }

there is a difference between connectionName and connections

kheide commented 9 years ago

Sorry to bring this up from the dead, but I have implemented this solution which works splendidly besides I am running into the exact same issue as @chipgata

Did any of you manage to find a solution?

Here is my console.log dump - exactly like chipgatas;

mongodevtenant:devtenant { mongodev: { collections: [ 'users', 'roles_usersusers_roles', 'roles' ], children: [ 'roles_usersusers_roles' ], joins: [ [Object], [Object] ] } }

Glavin001 commented 9 years ago

If memory serves me, I believe joins were not implemented for this Multitenancy, yet. It was on the TODO list however given that this is being effectively scrapped (see https://github.com/balderdashy/waterline/pull/787#issuecomment-87153003 ) I will not be implementing it. Unfortunately, I do not recommend using this code, as it was all tested, however it will not be maintained for the future.