1
votes

I'm trying to work with Strongloop loopback and loopback-component-passport but can't quite figure out how to get the relationships between the built in User model, and the loopback-component-passport provided models, to work

It looks like the relationships are provided in the loopback-component-passport/lib/models/*.json files but I get the following error:

Error: Cyclic dependency: "User"
  at visit (/work/node/loopback/myapp/node_modules/loopback-boot/node_modules/toposort/index.js:29:13)
  at visit (/work/node/loopback/myapp/node_modules/loopback-boot/node_modules/toposort/index.js:43:9)
  at toposort (/work/node/loopback/myapp/node_modules/loopback-boot/node_modules/toposort/index.js:22:22)
  at module.exports.exports (/work/node/loopback/myapp/node_modules/loopback-boot/node_modules/toposort/index.js:10:10)
  at sortByInheritance (/work/node/loopback/myapp/node_modules/loopback-boot/lib/compiler.js:248:21)
  at buildAllModelInstructions (/work/node/loopback/myapp/node_modules/loopback-boot/lib/compiler.js:205:10)
  at compile (/work/node/loopback/myapp/node_modules/loopback-boot/lib/compiler.js:79:27)
  at bootLoopBackApp (/work/node/loopback/myapp/node_modules/loopback-boot/index.js:128:22)
  at Object.<anonymous> (/work/node/loopback/myapp/server/server.js:44:1)
  at Module._compile (module.js:456:26)
[stu:/work/node/loopback/myapp (master)]$ 

This is my model-config.json

{
  "_meta": {
    "sources": [
      "loopback/common/models",
      "loopback/server/models",
      "../common/models",
      "./models",
      "../node_modules/loopback-component-passport/lib/models"
    ]
  },
  "User": {
    "dataSource": "db",
    "public": true
  },
  "UserCredential": {
    "dataSource": "db",
    "public": true
  },
  "UserIdentity": {
    "dataSource": "db",
    "public": true
  },
  "AccessToken": {
    "dataSource": "db",
    "public": false
  },
  "ACL": {
    "dataSource": "db",
    "public": false
  },
  "RoleMapping": {
    "dataSource": "db",
    "public": false
  },
  "Role": {
    "dataSource": "db",
    "public": false
  },
  "KnowBit": {
    "dataSource": "localpg",
    "public": true
  }
}

By enabling debug with loopback:boot:compiler

$ DEBUG=loopback:boot:compiler slc run

I'm able to get these logs:

[stu:/work/node/loopback/myapp (master)]$ DEBUG=loopback:boot:compiler slc run
INFO strong-agent API key not found, StrongOps dashboard reporting disabled.
Generate configuration with:
    npm install -g strongloop
    slc strongops
See http://docs.strongloop.com/strong-agent for more information.
supervisor running without clustering (unsupervised)
  loopback:boot:compiler Found model "AccessToken" - ../node_modules/loopback/common/models/access-token.json ../node_modules/loopback/common/models/access-token.js +0ms
  loopback:boot:compiler Found model "ACL" - ../node_modules/loopback/common/models/acl.json ../node_modules/loopback/common/models/acl.js +1ms
  loopback:boot:compiler Found model "Application" - ../node_modules/loopback/common/models/application.json ../node_modules/loopback/common/models/application.js +0ms
  loopback:boot:compiler Found model "Change" - ../node_modules/loopback/common/models/change.json ../node_modules/loopback/common/models/change.js +1ms
  loopback:boot:compiler Found model "Checkpoint" - ../node_modules/loopback/common/models/checkpoint.json ../node_modules/loopback/common/models/checkpoint.js +0ms
  loopback:boot:compiler Found model "Email" - ../node_modules/loopback/common/models/email.json ../node_modules/loopback/common/models/email.js +0ms
  loopback:boot:compiler Found model "RoleMapping" - ../node_modules/loopback/common/models/role-mapping.json ../node_modules/loopback/common/models/role-mapping.js +0ms
  loopback:boot:compiler Found model "Role" - ../node_modules/loopback/common/models/role.json ../node_modules/loopback/common/models/role.js +1ms
  loopback:boot:compiler Found model "Scope" - ../node_modules/loopback/common/models/scope.json ../node_modules/loopback/common/models/scope.js +0ms
  loopback:boot:compiler Found model "User" - ../node_modules/loopback/common/models/user.json ../node_modules/loopback/common/models/user.js +0ms
  loopback:boot:compiler Skipping unknown module source dir "loopback/server/models" +0ms
  loopback:boot:compiler Found model "KnowBit" - ../common/models/know-bit.json ../common/models/know-bit.js +1ms
  loopback:boot:compiler Model source code not found: undefined - TypeError: Arguments to path.join must be strings +0ms
  loopback:boot:compiler Found model "User" - ../common/models/user.json (no source file) +0ms
  loopback:boot:compiler Skipping unknown module source dir "./models" +0ms
  loopback:boot:compiler Found model "ApplicationCredential" - ../node_modules/loopback-component-passport/lib/models/application-credential.json ../node_modules/loopback-component-passport/lib/models/application-credential.js +1ms
  loopback:boot:compiler Found model "UserCredential" - ../node_modules/loopback-component-passport/lib/models/user-credential.json ../node_modules/loopback-component-passport/lib/models/user-credential.js +0ms
  loopback:boot:compiler Found model "UserIdentity" - ../node_modules/loopback-component-passport/lib/models/user-identity.json ../node_modules/loopback-component-passport/lib/models/user-identity.js +0ms
  loopback:boot:compiler Using model "User"
Configuration: {"dataSource":"db","public":true}
Definition {"name":"User","plural":"users","base":"User","relations":{"accessTokens":{"type":"hasMany","model":"AccessToken","foreignKey":"userId"},"identities":{"type":"hasMany","model":"UserIdentity","foreignKey":"userId"},"credentials":{"type":"hasMany","model":"UserCredential","foreignKey":"userId"}},"validations":[],"acls":[],"methods":[]} +0ms
  loopback:boot:compiler Using model "UserCredential"
Configuration: {"dataSource":"db","public":true}
Definition {"name":"UserCredential","base":"PersistedModel","properties":{"provider":{"type":"String","comments":"facebook, google, twitter, linkedin"},"authScheme":{"type":"String","comments":"oAuth, oAuth 2.0, OpenID, OpenID Connect"},"externalId":{"type":"String","comments":"The provider specific id"},"profile":{"type":"Object"},"credentials":{"type":"Object"},"created":"Date","modified":"Date","id":{"id":1,"generated":true}},"acls":[{"principalType":"ROLE","principalId":"$everyone","permission":"DENY"},{"principalType":"ROLE","principalId":"$owner","permission":"ALLOW"}],"relations":{"user":{"type":"belongsTo","model":"User","foreignKey":"userId"}}} +1ms
  loopback:boot:compiler Using model "UserIdentity"
Configuration: {"dataSource":"db","public":true}
Definition {"name":"UserIdentity","plural":"UserIdentities","base":"PersistedModel","properties":{"provider":{"type":"String","comments":"facebook, google, twitter, linkedin"},"authScheme":{"type":"String","comments":"oAuth, oAuth 2.0, OpenID, OpenID Connect"},"externalId":{"type":"String","comments":"The provider specific id"},"profile":{"type":"Object"},"credentials":{"type":"Object"},"created":"Date","modified":"Date","id":{"id":1,"generated":true}},"acls":[{"principalType":"ROLE","principalId":"$everyone","permission":"DENY"},{"principalType":"ROLE","principalId":"$owner","permission":"ALLOW"}],"relations":{"user":{"type":"belongsTo","model":"User","foreignKey":"userId"}}} +0ms
  loopback:boot:compiler Using model "AccessToken"
Configuration: {"dataSource":"db","public":false}
Definition {"name":"AccessToken","properties":{"id":{"type":"string","id":true},"ttl":{"type":"number","ttl":true,"default":1209600,"description":"time to live in seconds (2 weeks by default)"},"created":{"type":"Date"}},"relations":{"user":{"type":"belongsTo","model":"User","foreignKey":"userId"}},"acls":[{"principalType":"ROLE","principalId":"$everyone","permission":"DENY"},{"principalType":"ROLE","principalId":"$everyone","property":"create","permission":"ALLOW"}]} +0ms
  loopback:boot:compiler Using model "ACL"
Configuration: {"dataSource":"db","public":false}
Definition {"name":"ACL","properties":{"model":{"type":"string","description":"The name of the model"},"property":{"type":"string","description":"The name of the property, method, scope, or relation"},"accessType":"string","permission":"string","principalType":"string","principalId":"string","id":{"id":1,"generated":true}}} +0ms
  loopback:boot:compiler Using model "RoleMapping"
Configuration: {"dataSource":"db","public":false}
Definition {"name":"RoleMapping","description":"Map principals to roles","properties":{"id":{"type":"string","id":true,"generated":true},"principalType":{"type":"string","description":"The principal type, such as user, application, or role"},"principalId":"string"},"relations":{"role":{"type":"belongsTo","model":"Role","foreignKey":"roleId"}}} +0ms
  loopback:boot:compiler Using model "Role"
Configuration: {"dataSource":"db","public":false}
Definition {"name":"Role","properties":{"id":{"type":"string","id":true,"generated":true},"name":{"type":"string","required":true},"description":"string","created":"date","modified":"date"},"relations":{"principals":{"type":"hasMany","model":"RoleMapping","foreignKey":"roleId"}}} +0ms
  loopback:boot:compiler Using model "KnowBit"
Configuration: {"dataSource":"localpg","public":true}
Definition {"name":"KnowBit","base":"PersistedModel","idInjection":true,"properties":{"label":{"type":"string","required":true},"link":{"type":"string","required":true}},"validations":[],"relations":{},"acls":[],"methods":[]} +0ms
1

1 Answers

3
votes

TL;DR

I had specified the same string "User" for the name and base attributes, to fix the problem I changed the name value to lowercase "user"

Full story

ok so when I copied the

loopback-example-passport/common/models/user.json

file I mistyped and set the name='User' instead of 'user'

This did not fix the ultimate problem, but I think it explains the Cyclic dependency. The 'base' is the 'base' class of this model and I had that set to 'User' as well

This allows me to get past the model compilation stage but still leaves me without a configured relationship from 'user' to 'UserIdentity' which the passport code requires.

I think I'm on the right track though, the insight being that the loopback models in the strongloop/loopback-example-passport example are Overriding the built in classes with new model classes where the first letter of the class name is lower cased for example userCredential is the override for the built in model class UserCredential and so on, this is a bit confusing at first

{
  "name": "userCredential",
  "plural": "userCredentials",
  "base": "UserCredential",
...

But I think if I go through and check that I'm using the lower case versions things might work out

This was indeed the problem