beech-api 3.2.11 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ User: require('./packages/lib/beech')
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beech-api",
3
- "version": "3.2.11",
3
+ "version": "3.4.0",
4
4
  "description": "Command line interface for rapid Beech API development",
5
5
  "keywords": [
6
6
  "beech-api",
@@ -30,13 +30,32 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "fs": "0.0.1-security",
33
+ "jsonwebtoken": "^8.5.1",
33
34
  "log-update": "^4.0.0",
35
+ "md5": "^2.3.0",
34
36
  "mkdirp": "^0.5.1",
35
37
  "node-cmd": "^3.0.0",
36
- "path": "^0.12.7"
38
+ "passport": "^0.4.1",
39
+ "passport-facebook": "^3.0.0",
40
+ "passport-google-oauth": "^2.0.0",
41
+ "passport-jwt": "^4.0.0",
42
+ "passport-local": "^1.0.0",
43
+ "passport-oauth": "^1.0.0",
44
+ "path": "^0.12.7",
45
+ "cookie-parser": "1.4.3",
46
+ "cors": "^2.8.1",
47
+ "express": "4.16.3",
48
+ "express-session": "^1.17.1",
49
+ "express-validator": "2.21.0",
50
+ "module-alias": "^2.2.2",
51
+ "mysql": "^2.18.1",
52
+ "sequelize": "^5.21.6",
53
+ "walk": "^2.3.14",
54
+ "app-root-path": "^3.0.0"
37
55
  },
38
56
  "devDependencies": {
39
- "jest": "^25.2.7"
57
+ "jest": "^25.2.7",
58
+ "sequelize-cli": "^5.5.1"
40
59
  },
41
60
  "jest": {
42
61
  "prdFile": "./node_modules/beech-api/packages/cli/core/test/utils.js",
@@ -49,6 +49,7 @@ class Beech {
49
49
  .then(this.copy.bind(this, tmpDotSequelizercFile, pasteDotSequelizercFile))
50
50
  .then(this.copy.bind(this, tmpGloablConfigFile, pasteGloablConfigFile))
51
51
  .then(this.copy.bind(this, tmpGitignoreFile, pasteGitignoreFile))
52
+ .then(this.generateKeyConfigFile.bind(this, this.argument))
52
53
  .then(this.installPackage.bind(this, this.argument))
53
54
  .then(logUpdate("\n Processing  The `" + this.argument + "` application is creating...\n"))
54
55
  .catch((err) => {
@@ -99,7 +100,7 @@ class Beech {
99
100
  }
100
101
 
101
102
  successfully() {
102
- logUpdate('\n Passed  The project has been successfully created.\n\n $ cd ' + this.argument + '\n $ npm run start or yarn start');
103
+ logUpdate('\n Passed  The project has been successfully created.\n\n $ cd ' + this.argument + '\n $ npm start or yarn start');
103
104
  }
104
105
 
105
106
  async contentReplace(pathFile, textCondition) {
@@ -174,6 +175,58 @@ class Beech {
174
175
  });
175
176
  }
176
177
 
178
+ appKeyGenerator(length) {
179
+ return new Promise((resolve, reject) => {
180
+ try {
181
+ let md5 = require("md5");
182
+ let secret = require(__dirname + "/../../lib/salt").salt;
183
+ let result = '';
184
+ let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
185
+ let charactersLength = characters.length;
186
+ for ( var i = 0; i < length; i++ ) {
187
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
188
+ }
189
+ resolve(md5(result + secret));
190
+ } catch (error) {
191
+ reject(error);
192
+ }
193
+ });
194
+ }
195
+
196
+ generateKeyConfigFile(pjName) {
197
+ return new Promise((resolve, reject) => {
198
+ try {
199
+ this.fs.readFile(pjName + "/app.config.js", 'utf8', (err, data) => {
200
+ if (err) {
201
+ throw err;
202
+ } else {
203
+ // edit or add property
204
+ let buffer = Buffer.from(data);
205
+ let buf2str = buffer.toString();
206
+ let buf2json = JSON.parse(JSON.stringify(buf2str));
207
+ let buf2eval = eval(buf2json);
208
+ let oldSecret = buf2eval.main_config.app_secret;
209
+ // generate new key secret
210
+ this.appKeyGenerator(8).then(newAppSecret => {
211
+ // content replace
212
+ let text = data.replace(new RegExp(oldSecret, 'g'), newAppSecret);
213
+ // writing the file
214
+ this.fs.writeFile(pjName + "/app.config.js", text, 'utf8', (err) => {
215
+ if (err) {
216
+ throw err;
217
+ } else {
218
+ resolve("\n Passed  App secret it's new generated.");
219
+ }
220
+ });
221
+ });
222
+ }
223
+ });
224
+ } catch (error) {
225
+ reject(error);
226
+ }
227
+ });
228
+ }
229
+
177
230
  help() {
178
231
  return new Promise((resolve, reject) => {
179
232
  try {
@@ -0,0 +1,23 @@
1
+ const passport = require('passport');
2
+ module.exports = {
3
+ credentials: (req, res, next) => {
4
+ return passport.authenticate("jwt", {
5
+ session: false
6
+ }, (err, user, info) => {
7
+ if (err) {
8
+ console.log(err, info);
9
+ return next(err);
10
+ }
11
+ if (!user) {
12
+ return res.status(401).json({
13
+ code: 401,
14
+ status: 'error',
15
+ error: 'UNAUTHORIZED_USER'
16
+ });
17
+ }
18
+ // Forward user information to the next middleware
19
+ req.user = user;
20
+ next();
21
+ })(req, res, next);
22
+ }
23
+ }
@@ -0,0 +1,204 @@
1
+ const appRoot = require("app-root-path");
2
+ const fs = require("fs");
3
+ const passport_config_file = appRoot + "/passport.config.js";
4
+ const md5 = require("md5");
5
+ const secret = require("../../../lib/salt").salt;
6
+
7
+ module.exports = {
8
+ init() {
9
+ try {
10
+ if (fs.existsSync(passport_config_file)) {
11
+ var passport = require("passport"),
12
+ LocalStrategy = require("passport-local").Strategy,
13
+ GoogleStrategy = require("passport-google-oauth").OAuth2Strategy,
14
+ FacebookStrategy = require('passport-facebook').Strategy;
15
+ var passportJWT = require("passport-jwt"),
16
+ JWTStrategy = passportJWT.Strategy,
17
+ ExtractJWT = passportJWT.ExtractJwt;
18
+ const auth = require("./Credentials");
19
+ var passport_config = require(passport_config_file);
20
+ if (passport_config.jwt_allow) {
21
+ global.Credentials = auth.credentials;
22
+ } else {
23
+ global.Credentials = [];
24
+ return;
25
+ }
26
+ } else {
27
+ global.Credentials = [];
28
+ return;
29
+ }
30
+ // declare constant
31
+ let passportUsernameField = passport_config.model.username_field || "username";
32
+ let passportPasswordField = passport_config.model.password_field || "password";
33
+ let passportTable = passport_config.model.table || "users";
34
+ let passportFields = (passport_config.model.fields.length) ? passport_config.model.fields : ["id", "name", "email"];
35
+ // passport initial with token (encoder)
36
+ passport.use(new LocalStrategy({
37
+ usernameField: passportUsernameField,
38
+ passwordField: passportPasswordField
39
+ }, (username, password, done) => {
40
+ let pool = eval("mysql." + passport_config.model.name);
41
+ if (pool) {
42
+ pool.query("SELECT " + passportFields + " FROM ?? WHERE ?? = ? AND ?? = ?", [
43
+ passportTable,
44
+ passportUsernameField,
45
+ username,
46
+ passportPasswordField,
47
+ md5(password + secret)
48
+ ], (err, result) => {
49
+ if (err) {
50
+ return done(err);
51
+ } else {
52
+ return done(null, JSON.parse(JSON.stringify(result[0] || null)));
53
+ }
54
+ });
55
+ } else {
56
+ return done(null, null, true);
57
+ }
58
+ }));
59
+ // passport jwt payload (decoder)
60
+ passport.use(new JWTStrategy({
61
+ jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
62
+ secretOrKey: passport_config.secret
63
+ }, (jwtPayload, done) => {
64
+ let pool = eval("mysql." + passport_config.model.name);
65
+ if (pool) {
66
+ pool.query("SELECT " + passportFields + " FROM ?? WHERE id = ?", [
67
+ passportTable,
68
+ jwtPayload.id
69
+ ], (err, result) => {
70
+ if (err) {
71
+ return done(err);
72
+ } else {
73
+ return done(null, JSON.parse(JSON.stringify(result[0] || null)));
74
+ }
75
+ });
76
+ } else {
77
+ return done(null, null, true);
78
+ }
79
+ }));
80
+
81
+ // declare head authentication enpoint for all strategy
82
+ let auth_endpoint = (passport_config.auth_endpoint) ? (passport_config.auth_endpoint[0] === "/" ? passport_config.auth_endpoint : "/" + passport_config.auth_endpoint) : "/authentication";
83
+
84
+ /**
85
+ * Passport Google Strategy
86
+ *
87
+ */
88
+ let google_callback_endpoint = (passport_config.strategy.google.callback_endpoint) ? (passport_config.strategy.google.callback_endpoint[0] === "/" ? passport_config.strategy.google.callback_endpoint : "/" + passport_config.strategy.google.callback_endpoint) : "/google/callback";
89
+ passport.use(new GoogleStrategy({
90
+ clientID: passport_config.strategy.google.client_id,
91
+ clientSecret: passport_config.strategy.google.client_secret,
92
+ callbackURL: auth_endpoint + google_callback_endpoint
93
+ }, (accessToken, refreshToken, profile, done) => {
94
+ // find google user
95
+ let googleIdField = (passport_config.strategy.google.google_id_field) ? passport_config.strategy.google.google_id_field : "google_id";
96
+ this.findOrCreate(passport_config, "google", passportFields, passportTable, accessToken, refreshToken, profile, googleIdField, (err, res, dbFailed) => {
97
+ if (err) {
98
+ return done(err);
99
+ } else {
100
+ return done(err, res, dbFailed);
101
+ }
102
+ });
103
+ }));
104
+
105
+ /**
106
+ * Passport Facebook Strategy
107
+ *
108
+ */
109
+ let facebook_callback_endpoint = (passport_config.strategy.facebook.callback_endpoint) ? (passport_config.strategy.facebook.callback_endpoint[0] === "/" ? passport_config.strategy.facebook.callback_endpoint : "/" + passport_config.strategy.facebook.callback_endpoint) : "/facebook/callback";
110
+ passport.use(new FacebookStrategy({
111
+ clientID: passport_config.strategy.facebook.app_id,
112
+ clientSecret: passport_config.strategy.facebook.app_secret,
113
+ callbackURL: auth_endpoint + facebook_callback_endpoint
114
+ }, (accessToken, refreshToken, profile, done) => {
115
+
116
+ console.log(accessToken, refreshToken, profile);
117
+
118
+ // fix somthing went wrong
119
+
120
+
121
+ //return
122
+
123
+ // find facebook user
124
+ let faecbookIdField = (passport_config.strategy.facebook.google_id_field) ? passport_config.strategy.facebook.facebook_id_field : "facebook_id";
125
+ this.findOrCreate(passport_config, "facebook", passportFields, passportTable, accessToken, refreshToken, profile, faecbookIdField, (err, res, dbFailed) => {
126
+ if (err) {
127
+ return done(err);
128
+ } else {
129
+ return done(err, res, dbFailed);
130
+ }
131
+ });
132
+ }
133
+ ));
134
+ } catch (error) {
135
+ throw error;
136
+ }
137
+ },
138
+ findOrCreate(passport_config, strategy_name, passportFields, passportTable, accessToken, refreshToken, profile, googleIdField, cb) {
139
+ let pool = eval("mysql." + passport_config.model.name);
140
+ if (pool) {
141
+ pool.query("SELECT " + passportFields + " FROM ?? WHERE ?? = ?", [
142
+ passportTable,
143
+ googleIdField,
144
+ profile.id
145
+ ], (err, result) => {
146
+ if (err) {
147
+ cb(err);
148
+ } else {
149
+ // declare data response
150
+ let data = {};
151
+ // check strategy name for store
152
+ if (strategy_name == "google") {
153
+ if (!result[0]) { // find not found and create
154
+ // filter fields
155
+ let fields = [].concat.apply([], [
156
+ (passport_config.strategy.google.profile_fields.name) ? passport_config.strategy.google.profile_fields.name : null,
157
+ (passport_config.strategy.google.profile_fields.email) ? passport_config.strategy.google.profile_fields.email : null,
158
+ (passport_config.strategy.google.profile_fields.photos) ? passport_config.strategy.google.profile_fields.photos : null,
159
+ (passport_config.strategy.google.profile_fields.locate) ? passport_config.strategy.google.profile_fields.locate : null
160
+ ].filter((el) => el != null));
161
+ // fileter values
162
+ let values = [].concat.apply([], [
163
+ (passport_config.strategy.google.profile_fields.name) ? profile.displayName : null,
164
+ (passport_config.strategy.google.profile_fields.email) ? profile.emails[0].value : null,
165
+ (passport_config.strategy.google.profile_fields.photos) ? profile.photos[0].value : null,
166
+ (passport_config.strategy.google.profile_fields.locate) ? profile._json.locale : null
167
+ ].filter((el) => el != null));
168
+ // Store google profile
169
+ pool.query("INSERT INTO ??(??,??,??,??) VALUES(?,?,?,?)", [
170
+ passportTable,
171
+ passport_config.model.username_field || "username",
172
+ passport_config.model.password_field || "password",
173
+ googleIdField,
174
+ fields,
175
+ profile.emails[0].value.split("@")[0],
176
+ md5(profile.id + secret),
177
+ profile.id,
178
+ values
179
+ ], (err, result) => {
180
+ data.result = result;
181
+ data.google = profile;
182
+ cb(err, data);
183
+ });
184
+ } else { // find found
185
+ let users = {};
186
+ users.google = profile;
187
+ users.google.accessToken = accessToken;
188
+ users.google.refreshToken = refreshToken;
189
+ users.user = result[0];
190
+ cb(err, users);
191
+ }
192
+ } else if (strategy_name == "facebook") {
193
+
194
+ console.log(result)
195
+
196
+ }
197
+ }
198
+ });
199
+ } else {
200
+ cb(null, null, true);
201
+ }
202
+ }
203
+
204
+ }
@@ -1,11 +1,11 @@
1
1
  module.exports = {
2
2
  /**
3
- * Server configuration
3
+ * Service configuration
4
4
  *
5
- * @exports app_port Listening start server
6
- * @exports app_host Server http host
7
- * @exports client_host Production host
8
- * @exports app_secret App secret using the API
5
+ * @exports app_port : Listening for start service
6
+ * @exports app_host : Server http localhost
7
+ * @exports client_host : Production http client host
8
+ * @exports app_secret : App secret key for request with endpoints
9
9
  *
10
10
  */
11
11
  main_config: {
@@ -15,20 +15,23 @@ module.exports = {
15
15
  app_secret: ["2cc118cd91b52ff99e3c005ddced76fb"]
16
16
  },
17
17
 
18
+ // Add-on it's work when enable. You can enable add-on by run CMD `$ beech add-on init`.
19
+ addOn: true,
20
+
18
21
  /**
19
22
  * Database configuration (mutiple connection) currenty support for MySQL
20
23
  *
21
- * @exports name meaning The Connection name
22
- * @exports host meaning The Host address
23
- * @exports username meaning The Host username connection
24
- * @exports password meaning Host The password connection
25
- * @exports database meaning The database name
26
- * @exports port meaning The sql port (default 3306)
27
- * @exports charset meaning The character encoding
28
- * @exports isConnect meaning The sql connection flag (boolean)
24
+ * @exports name : The Connection name
25
+ * @exports host : The Host address
26
+ * @exports username : The Host username connection
27
+ * @exports password : Host The password connection
28
+ * @exports database : The database name
29
+ * @exports port : The sql port (default 3306)
30
+ * @exports charset : The character encoding
31
+ * @exports is_connect : The sql connection flag (boolean)
29
32
  *
30
33
  */
31
- mySqlConfig: [
34
+ mysql_config: [
32
35
  {
33
36
  name: "default_db",
34
37
  host: "127.0.0.1",
@@ -37,7 +40,7 @@ module.exports = {
37
40
  database: "example1_db",
38
41
  port: "3306",
39
42
  charset: "utf8",
40
- isConnect: false
43
+ is_connect: false
41
44
  },
42
45
  {
43
46
  name: "second_db",
@@ -47,7 +50,7 @@ module.exports = {
47
50
  database: "example2_db",
48
51
  port: "3306",
49
52
  charset: "utf8",
50
- isConnect: false
53
+ is_connect: false
51
54
  }
52
55
  ]
53
56
  };
@@ -0,0 +1,84 @@
1
+ module.exports = {
2
+ // Allow using jwt
3
+ jwt_allow: true,
4
+
5
+ // Custom authenticaiton endpoint, default `/authentication`
6
+ auth_endpoint: "",
7
+
8
+ // Assign your jwt secret key
9
+ secret: "your_jwt_secret",
10
+
11
+ // Set token expiry time (seconds), default expired in 24 hr.
12
+ token_expired: 86400,
13
+
14
+ model: {
15
+ // Main mysql connection same name inside `app.config.js` file
16
+ name: "default_db",
17
+ // The user table name for store your authenticate, default table `users`
18
+ table: "",
19
+ // The fields for authenticate, default fields: `username` and `password`
20
+ username_field: "",
21
+ password_field: "",
22
+ // Show JWT fields, default show fields: ["id", "name", "email"]
23
+ fields: []
24
+ },
25
+
26
+ // Allow using with app_secret requset
27
+ app_secret_allow: true,
28
+
29
+ // Official strategy
30
+ strategy: {
31
+ /**
32
+ * The Client Id and Client Secret needed to authenticate with Google can be set up from the Google Developers Console (https://console.developers.google.com/)
33
+ * You may also need to enable Google+ API in the developer console, otherwise user profile data may not be fetched.
34
+ * Now Google supports authentication with oAuth 2.0.
35
+ *
36
+ */
37
+ google: {
38
+ // Allow using google strategy
39
+ allow: true,
40
+ // Google ID fields, default field: `google_id`
41
+ google_id_field: "",
42
+ // User profile fields, default fields: `name`, `email`, `photos`, `locate`
43
+ profile_fields: {
44
+ name: "name",
45
+ email: "email",
46
+ photos: "profile_url",
47
+ locate: "" // If not storing set to null
48
+ },
49
+ // Google development Credentials OAuth 2.0 Client IDs
50
+ client_id: GOOGLE_CLIENT_ID,
51
+ client_secret: GOOGLE_CLIENT_SECRET,
52
+ // Callback endpoint default `/google/callback`
53
+ callback_endpoint: ""
54
+ },
55
+
56
+ /**
57
+ * The Facebook strategy allows users to log in to a web application using their Facebook account. Internally, Facebook authentication works using OAuth 2.0.
58
+ * Support for Facebook is implemented by the passport-facebook (https://github.com/jaredhanson/passport-facebook) module.
59
+ *
60
+ * In order to use Facebook authentication, you must first create an app at Facebook Developers. (https://developers.facebook.com/apps) When created, an app is assigned an App ID and App Secret.
61
+ * Your application must also implement a redirect URL, to which Facebook will redirect users after they have approved access for your application.
62
+ *
63
+ */
64
+ facebook: {
65
+ // Allow using facebook strategy
66
+ allow: true,
67
+ // Facebook ID fields, default field: `facebook_id`
68
+ facebook_id_field: "",
69
+ // User profile fields, default fields: `name`, `email`, `photos`, `locate`
70
+ profile_fields: {
71
+ name: "name",
72
+ email: "email",
73
+ photos: "profile_url",
74
+ locate: "" // If not storing set to null
75
+ },
76
+ // Facebook development Credentials OAuth 2.0
77
+ app_id: FACEBOOK_APP_ID,
78
+ app_secret: FACEBOOK_APP_SECRET,
79
+ // Callback endpoint default `/facebook/callback`
80
+ callback_endpoint: ""
81
+ },
82
+
83
+ }
84
+ }
@@ -2,9 +2,16 @@ exports.mySqlConnection = () => {
2
2
  return new Promise((resolve, reject) => {
3
3
  try {
4
4
  global.mysql = {};
5
+ var headDbShow = true;
5
6
  // loop database connection
6
- _config_.mySqlConfig.map((val, index) => {
7
- if (val.isConnect) {
7
+ _config_.mysql_config.map((val, index) => {
8
+ if (val.is_connect) {
9
+ // show only one text db connnections
10
+ if (headDbShow) {
11
+ console.log('\n Passed  Database is connected at:');
12
+ headDbShow = false;
13
+ }
14
+ // db connection config
8
15
  let connection = _mysql_.createConnection({
9
16
  host: val.host,
10
17
  user: val.username,
@@ -13,12 +20,13 @@ exports.mySqlConnection = () => {
13
20
  charset: val.charset,
14
21
  port: val.port
15
22
  })
23
+ // db connecting
16
24
  connection.connect((err) => {
17
25
  if (!err) {
18
26
  mysql[val.name] = connection;
19
- console.log(' Passed  Database is connected at [' + val.name, '->', connection.config.database + ':' + connection.config.port + ']');
27
+ console.log(' - ' + val.name, '->', connection.config.database + ':' + connection.config.port + '');
20
28
  // checking for resolve
21
- if ((index + 1) == _config_.mySqlConfig.length) {
29
+ if ((index + 1) == _config_.mysql_config.length) {
22
30
  resolve(true);
23
31
  }
24
32
  } else {
@@ -28,7 +36,7 @@ exports.mySqlConnection = () => {
28
36
  })
29
37
  } else {
30
38
  // checking for resolve
31
- if ((index + 1) == _config_.mySqlConfig.length) {
39
+ if ((index + 1) == _config_.mysql_config.length) {
32
40
  resolve(true);
33
41
  }
34
42
  }
@@ -0,0 +1,9 @@
1
+ //const Users = require("@/models/Users");
2
+
3
+ exports.init = () => {
4
+
5
+ //Users.findAll().then(console.log)
6
+
7
+ console.log("Add-on is work!")
8
+
9
+ }
@@ -3,7 +3,7 @@ exports.init = () => {
3
3
  // Initiate with basic request currently support GET, POST, PUT, PATCH and DELETE
4
4
 
5
5
  /@GET/
6
- endpoint.get('{{endpoint}}', (req, res) => {
6
+ endpoint.get('{{endpoint}}', Credentials, (req, res) => {
7
7
  // basic get method
8
8
  let data = {};
9
9
  data.code = 200;
@@ -13,7 +13,7 @@ exports.init = () => {
13
13
  });
14
14
 
15
15
  /@POST/
16
- endpoint.post('{{endpoint}}', (req, res) => {
16
+ endpoint.post('{{endpoint}}', Credentials, (req, res) => {
17
17
  /**
18
18
  * @param Integer id
19
19
  *
@@ -28,7 +28,7 @@ exports.init = () => {
28
28
  });
29
29
 
30
30
  /@PUT/
31
- endpoint.put('{{endpoint}}/:id', (req, res) => {
31
+ endpoint.put('{{endpoint}}/:id', Credentials, (req, res) => {
32
32
  /**
33
33
  * @param Integer id
34
34
  *
@@ -43,7 +43,7 @@ exports.init = () => {
43
43
  });
44
44
 
45
45
  /@PATCH/
46
- endpoint.patch('{{endpoint}}/:id', (req, res) => {
46
+ endpoint.patch('{{endpoint}}/:id', Credentials, (req, res) => {
47
47
  /**
48
48
  * @param Integer id
49
49
  *
@@ -58,7 +58,7 @@ exports.init = () => {
58
58
  });
59
59
 
60
60
  /@DELETE/
61
- endpoint.delete('{{endpoint}}/:id', (req, res) => {
61
+ endpoint.delete('{{endpoint}}/:id', Credentials, (req, res) => {
62
62
  /**
63
63
  * @param Integer id
64
64
  *
@@ -12,3 +12,6 @@ The following commands are available:
12
12
  You might using <special> `--require=Model1,Model2,..`
13
13
  for require model file(s) in generate processing
14
14
  $ beech make <model> --model Create a new models file
15
+ $ beech passport init Initialize authentication with passport-jwt
16
+ $ beech add-on init Initialize add-on file
17
+