alchemymvc 1.2.2 → 1.2.5

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.
@@ -317,7 +317,7 @@ Model.prepareProperty('sort', function sort() {
317
317
  *
318
318
  * @author Jelle De Loecker <jelle@develry.be>
319
319
  * @since 1.0.0
320
- * @version 1.1.0
320
+ * @version 1.2.5
321
321
  *
322
322
  * @param {String} value The value in the url
323
323
  * @param {String} name The name of the url parameter
@@ -326,7 +326,7 @@ Model.prepareProperty('sort', function sort() {
326
326
  *
327
327
  * @return {Pledge}
328
328
  */
329
- Model.setStatic(function checkPathValue(value, name, field_name, conduit) {
329
+ Model.setStatic(async function checkPathValue(value, name, field_name, conduit) {
330
330
 
331
331
  var instance,
332
332
  pledge,
@@ -352,9 +352,17 @@ Model.setStatic(function checkPathValue(value, name, field_name, conduit) {
352
352
  // Look for the wanted field
353
353
  crit.where(field_name).equals(value);
354
354
 
355
- pledge = instance.find('first', crit);
355
+ let result = await instance.find('first', crit);
356
356
 
357
- return pledge;
357
+ if (result) {
358
+ let found_value = result[field_name];
359
+
360
+ if (found_value != value && !Object.alike(value, found_value)) {
361
+ conduit.rewriteRequestRouteParam(name, found_value);
362
+ }
363
+ }
364
+
365
+ return result;
358
366
  });
359
367
 
360
368
  /**
@@ -362,7 +370,7 @@ Model.setStatic(function checkPathValue(value, name, field_name, conduit) {
362
370
  *
363
371
  * @author Jelle De Loecker <jelle@develry.be>
364
372
  * @since 0.2.0
365
- * @version 1.0.7
373
+ * @version 1.2.4
366
374
  *
367
375
  * @return {Alchemy.Field}
368
376
  */
@@ -380,12 +388,10 @@ Model.setStatic(function addField(name, type, options) {
380
388
  // Add it to the Document class
381
389
  this.Document.setFieldGetter(name);
382
390
 
383
- // Add the field to the client document too, if it's not private
384
- if (!field.is_private) {
385
- // False means it should not be set on the server implementation
386
- // (because that's where it's coming from)
387
- this.ClientDocument.setFieldGetter(name, null, null, false);
388
- }
391
+ // False means it should not be set on the server implementation
392
+ // (because that's where it's coming from)
393
+ // Yes, this also sets private fields on the server-side client document.
394
+ this.ClientDocument.setFieldGetter(name, null, null, false);
389
395
  }
390
396
 
391
397
  return field;
@@ -409,18 +415,16 @@ Model.setStatic(function addBehaviour(behaviour_name, options) {
409
415
  *
410
416
  * @author Jelle De Loecker <jelle@develry.be>
411
417
  * @since 0.2.0
412
- * @version 0.2.0
418
+ * @version 1.2.4
413
419
  */
414
420
  Model.setStatic(function addAssociation(type, alias, model_name, options) {
415
421
  var data = this.schema.addAssociation(type, alias, model_name, options);
416
422
  this.Document.setAliasGetter(data.alias);
417
423
 
418
- // Add the alias to the client document too, if it's not private
419
- if (!options || !options.is_private) {
420
- // False means it should not be set on the server implementation
421
- // (because that's where it's coming from)
422
- this.ClientDocument.setAliasGetter(name);
423
- }
424
+ // False means it should not be set on the server implementation
425
+ // (because that's where it's coming from)
426
+ // Yes, this also sets private fields on the server-side client document.
427
+ this.ClientDocument.setAliasGetter(name);
424
428
  });
425
429
 
426
430
  /**
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@develry.be>
5
5
  * @since 0.2.0
6
- * @version 1.0.0
6
+ * @version 1.2.5
7
7
  */
8
8
  var Route = Function.inherits('Alchemy.Base', function Route(router, paths, options) {
9
9
 
@@ -41,6 +41,10 @@ var Route = Function.inherits('Alchemy.Base', function Route(router, paths, opti
41
41
  this.breadcrumb = '';
42
42
  this.has_breadcrumb_assignments = false;
43
43
 
44
+ // The optional permissions to check
45
+ this.permission = null;
46
+ this.has_permission_assignments = false;
47
+
44
48
  // If no fnc is given, these will be called
45
49
  this.controller = null;
46
50
  this.action = null;
@@ -84,6 +88,35 @@ Route.setMethod(function setBreadcrumb(breadcrumb) {
84
88
  this.breadcrumb = breadcrumb;
85
89
  });
86
90
 
91
+ /**
92
+ * Set the permissions for this route
93
+ *
94
+ * @author Jelle De Loecker <jelle@elevenways.be>
95
+ * @since 1.2.5
96
+ * @version 1.2.5
97
+ *
98
+ * @param {String|String[]} permission
99
+ */
100
+ Route.setMethod(function setPermission(permission) {
101
+
102
+ if (!permission) {
103
+ return;
104
+ }
105
+
106
+ permission = Array.cast(permission);
107
+
108
+ this.permission = permission;
109
+
110
+ for (let entry of permission) {
111
+ let assignments = entry.assignments();
112
+
113
+ if (assignments.length) {
114
+ this.has_permission_assignments = true;
115
+ break;
116
+ }
117
+ }
118
+ });
119
+
87
120
  /**
88
121
  * Compile paths for this route
89
122
  *
@@ -135,7 +168,7 @@ Route.setMethod(function setPaths(paths) {
135
168
  *
136
169
  * @author Jelle De Loecker <jelle@develry.be>
137
170
  * @since 0.2.0
138
- * @version 1.0.3
171
+ * @version 1.2.5
139
172
  *
140
173
  * @param {String} method
141
174
  * @param {String} path The path (without the prefix)
@@ -145,19 +178,13 @@ Route.setMethod(function setPaths(paths) {
145
178
  */
146
179
  Route.setMethod(function match(conduit, method, path, prefix) {
147
180
 
148
- var definition,
149
- params,
150
- pledge,
151
- result,
152
- temp,
153
- type,
154
- i;
181
+ conduit.markRouteAsTested(this);
155
182
 
156
183
  if (this.methods && this.methods.indexOf(method) == -1) {
157
184
  return false;
158
185
  }
159
186
 
160
- result = false;
187
+ let result = false;
161
188
 
162
189
  if (!prefix) {
163
190
  prefix = '';
@@ -181,17 +208,17 @@ Route.setMethod(function match(conduit, method, path, prefix) {
181
208
  if (this.paths[prefix]) {
182
209
 
183
210
  // Get the path definition, including the regex & keys array
184
- definition = this.paths[prefix];
211
+ let definition = this.paths[prefix];
185
212
 
186
213
  // Test it
187
- temp = definition.test(path, conduit);
214
+ let temp = definition.test(path, conduit);
188
215
 
189
216
  if (!temp) {
190
217
  return false;
191
218
  }
192
219
 
193
220
  if (temp.then) {
194
- pledge = new Blast.Classes.Pledge();
221
+ let pledge = new Blast.Classes.Pledge();
195
222
 
196
223
  temp.then(function gotValue(values) {
197
224
 
@@ -295,12 +322,48 @@ Route.setMethod(function checkPolicy(conduit) {
295
322
  return false;
296
323
  });
297
324
 
325
+ /**
326
+ * Check the permission
327
+ *
328
+ * @author Jelle De Loecker <jelle@elevenways.be>
329
+ * @since 1.2.5
330
+ * @version 1.2.5
331
+ *
332
+ * @param {Conduit} conduit
333
+ */
334
+ Route.setMethod(function checkPermission(conduit) {
335
+
336
+ // Check the rouer section first
337
+ if (!this.router.checkPermission(conduit)) {
338
+ return false;
339
+ }
340
+
341
+ if (!this.permission) {
342
+ return true;
343
+ }
344
+
345
+ let permission;
346
+
347
+ for (permission of this.permission) {
348
+
349
+ if (this.has_permission_assignments) {
350
+ permission = permission.assign(conduit.route_string_parameters).toLowerCase();
351
+ }
352
+
353
+ if (conduit.hasPermission(permission)) {
354
+ return true;
355
+ }
356
+ }
357
+
358
+ return false;
359
+ });
360
+
298
361
  /**
299
362
  * Call the handler for this route
300
363
  *
301
364
  * @author Jelle De Loecker <jelle@develry.be>
302
365
  * @since 0.2.0
303
- * @version 1.1.5
366
+ * @version 1.2.5
304
367
  *
305
368
  * @param {Conduit} conduit
306
369
  * @param {Array} parameters
@@ -314,6 +377,17 @@ Route.setMethod(async function callHandler(conduit, parameters) {
314
377
 
315
378
  if (this.fnc) {
316
379
 
380
+ if (!this.checkPermission(conduit)) {
381
+
382
+ let user = conduit.session('UserData');
383
+
384
+ if (!user) {
385
+ return conduit.notAuthorized();
386
+ } else {
387
+ return conduit.forbidden();
388
+ }
389
+ }
390
+
317
391
  if (this.options.policy && !await this.checkPolicy(conduit)) {
318
392
  return conduit.notAuthorized();
319
393
  }
@@ -403,25 +477,20 @@ Route.setMethod(async function callHandler(conduit, parameters) {
403
477
  *
404
478
  * @author Jelle De Loecker <jelle@develry.be>
405
479
  * @since 0.3.0
406
- * @version 0.5.0
480
+ * @version 1.2.5
407
481
  *
408
482
  * @param {Object} parameters (optional)
409
483
  * @param {Object|Alchemy.Conduit} conduit Conduit or options (optional)
410
484
  */
411
485
  Route.setMethod(function generateUrl(parameters, conduit) {
412
486
 
413
- var locale,
414
- mount,
415
- path,
416
- url,
417
- i;
418
-
419
487
  // Use the "no locale" by default
420
- locale = '';
488
+ let locale = '';
421
489
 
422
490
  // If a conduit is given, get the locale
423
491
  if (conduit) {
424
492
  if (conduit.locales) {
493
+ let i;
425
494
  for (i = 0; i < conduit.locales.length; i++) {
426
495
  if (this.paths[conduit.locales[i]]) {
427
496
  locale = conduit.locales[i];
@@ -435,45 +504,7 @@ Route.setMethod(function generateUrl(parameters, conduit) {
435
504
  }
436
505
  }
437
506
 
438
- // Get the path to use for the url generation
439
- path = this.paths[locale];
440
-
441
- if (!path) {
442
- return null;
443
- }
444
-
445
- if (path && path.source) {
446
- path = path.source;
447
- }
448
-
449
- // Remove type definitions from the path
450
- path = path.replace(/\[.*\]\:/g, ':');
451
-
452
- if (!parameters && conduit) {
453
- if (conduit.param) {
454
- parameters = conduit.param();
455
- } else if (conduit.parameters) {
456
- paramers = conduit.parameters;
457
- }
458
- }
459
-
460
- if (parameters) {
461
- url = path.fillPlaceholders(parameters, true);
462
- } else {
463
- url = path;
464
- }
465
-
466
- mount = this.router.getFullMount();
467
-
468
- if (mount.length > 1) {
469
- url = mount + url;
470
- }
471
-
472
- if (locale) {
473
- url = '/' + locale + url;
474
- }
475
-
476
- return url;
507
+ return Router.getUrl(this.name, parameters, {locale});
477
508
  });
478
509
 
479
510
  /**
@@ -12,7 +12,7 @@ var Url = alchemy.use('url'),
12
12
  *
13
13
  * @author Jelle De Loecker <jelle@develry.be>
14
14
  * @since 0.2.0
15
- * @version 1.1.0
15
+ * @version 1.2.5
16
16
  */
17
17
  var RouterClass = Function.inherits('Alchemy.Base', function Router(name, mount, parent) {
18
18
 
@@ -52,6 +52,9 @@ var RouterClass = Function.inherits('Alchemy.Base', function Router(name, mount,
52
52
  // Routes inside this router
53
53
  this.routes = new Deck();
54
54
 
55
+ // Optional permissions to check for
56
+ this.permissions = null;
57
+
55
58
  this.generateSectionIdentifier();
56
59
  });
57
60
 
@@ -67,6 +70,66 @@ RouterClass.setProperty('default_route_settings', {
67
70
  weight : 10
68
71
  });
69
72
 
73
+ /**
74
+ * Add a required permission
75
+ *
76
+ * @author Jelle De Loecker <jelle@elevenways.be>
77
+ * @since 1.2.5
78
+ * @version 1.2.5
79
+ *
80
+ * @param {String|String[]} permission
81
+ */
82
+ RouterClass.setMethod(function requirePermission(permission) {
83
+
84
+ if (!permission) {
85
+ return;
86
+ }
87
+
88
+ if (!this.permissions) {
89
+ this.permissions = [];
90
+ }
91
+
92
+ this.permissions.include(permission);
93
+ });
94
+
95
+ /**
96
+ * Check the permission
97
+ *
98
+ * @author Jelle De Loecker <jelle@elevenways.be>
99
+ * @since 1.2.5
100
+ * @version 1.2.5
101
+ *
102
+ * @param {Conduit} conduit
103
+ *
104
+ * @return {Boolean}
105
+ */
106
+ RouterClass.setMethod(function checkPermission(conduit) {
107
+
108
+ // Does this section require a permission?
109
+ if (this.permissions?.length) {
110
+ let permission,
111
+ has_permission;
112
+
113
+ for (permission of this.permissions) {
114
+ if (conduit.hasPermission(permission)) {
115
+ has_permission = true;
116
+ break;
117
+ }
118
+ }
119
+
120
+ if (!has_permission) {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ // If this section has a parent, check those permissions too
126
+ if (this.parent) {
127
+ return this.parent.checkPermission(conduit);
128
+ }
129
+
130
+ return true;
131
+ });
132
+
70
133
  /**
71
134
  * Get the complete section identifier
72
135
  *
@@ -424,7 +487,7 @@ RouterClass.setMethod(function getRouteByName(name) {
424
487
  *
425
488
  * @author Jelle De Loecker <jelle@develry.be>
426
489
  * @since 0.2.0
427
- * @version 1.0.0
490
+ * @version 1.2.5
428
491
  *
429
492
  * @param {Conduit} conduit
430
493
  * @param {Router} section
@@ -462,6 +525,16 @@ RouterClass.setMethod(async function getMiddleware(conduit, section, path, prefi
462
525
  for (i = 0; i < routes.length; i++) {
463
526
  route = routes[i];
464
527
 
528
+ // If this route matches the main route of the conduit,
529
+ // don't look for more middleware in this section
530
+ if (conduit.route && conduit.route == route) {
531
+ break;
532
+ }
533
+
534
+ if (conduit.hasRouteBeenTested(route)) {
535
+ continue;
536
+ }
537
+
465
538
  if (route.is_middleware) {
466
539
 
467
540
  temp = route.match(conduit, conduit.method, sectionPath, prefix);
@@ -690,7 +763,7 @@ RouterClass.setMethod(function setOption(name, value) {
690
763
  *
691
764
  * @author Jelle De Loecker <jelle@develry.be>
692
765
  * @since 0.2.0
693
- * @version 1.1.0
766
+ * @version 1.2.5
694
767
  *
695
768
  * @param {Object} args
696
769
  * @param {String} args.name Optional route name
@@ -741,6 +814,10 @@ RouterClass.setMethod(function add(args) {
741
814
  route.methods = args.methods;
742
815
  route.setBreadcrumb(args.breadcrumb);
743
816
 
817
+ if (args.permission) {
818
+ route.setPermission(args.permission);
819
+ }
820
+
744
821
  if (args.breadcrumb) {
745
822
  breadcrumb_options = {
746
823
  route : route
@@ -287,7 +287,7 @@ Schema.setMethod(function getAssociationArguments(locality, alias, modelName, op
287
287
  *
288
288
  * @author Jelle De Loecker <jelle@develry.be>
289
289
  * @since 0.2.0
290
- * @version 1.2.2
290
+ * @version 1.2.4
291
291
  *
292
292
  * @param {String} alias
293
293
  * @param {String} modelname
@@ -382,7 +382,8 @@ Schema.setMethod(function addAssociation(type, alias, modelName, options) {
382
382
  doc_class.setFieldGetter(options.localKey);
383
383
  }
384
384
 
385
- if (client_doc && !options.is_private) {
385
+ // Also set is_private fields on the server-side client_doc!
386
+ if (client_doc) {
386
387
  client_doc.setFieldGetter(options.localKey, null, null, false);
387
388
  }
388
389
  }
@@ -396,7 +397,8 @@ Schema.setMethod(function addAssociation(type, alias, modelName, options) {
396
397
  if (constructor) {
397
398
  doc_class.setAliasGetter(alias);
398
399
 
399
- if (client_doc && !options.is_private) {
400
+ // Also set is_private fields on the server-side client_doc!
401
+ if (client_doc) {
400
402
  client_doc.setAliasGetter(alias);
401
403
  }
402
404
  }
@@ -414,6 +414,37 @@ Schema.setMethod(function toHawkejs(wm) {
414
414
  return result;
415
415
  });
416
416
 
417
+ /**
418
+ * Get all the private fields
419
+ *
420
+ * @author Jelle De Loecker <jelle@elevenways.be>
421
+ * @since 1.2.4
422
+ * @version 1.2.4
423
+ *
424
+ * @return {Array}
425
+ */
426
+ Schema.setMethod(function getPrivateFields() {
427
+
428
+ let result = [],
429
+ field,
430
+ i;
431
+
432
+ // Get the sorted fields
433
+ let fields = this.getSorted(false);
434
+
435
+ for (i = 0; i < fields.length; i++) {
436
+ field = fields[i];
437
+
438
+ if (field.is_private) {
439
+ field = Object.create(field);
440
+ field.schema = null;
441
+ result.push(field);
442
+ }
443
+ }
444
+
445
+ return result;
446
+ });
447
+
417
448
  /**
418
449
  * Add a field to this schema
419
450
  *
@@ -606,7 +637,7 @@ Schema.setMethod(function getField(name) {
606
637
  *
607
638
  * @author Jelle De Loecker <jelle@develry.be>
608
639
  * @since 0.2.0
609
- * @version 1.1.0
640
+ * @version 1.2.5
610
641
  *
611
642
  * @param {String} name
612
643
  *
@@ -629,9 +660,14 @@ Schema.setMethod(function getFieldChain(name) {
629
660
  // Iterate over all the pieces
630
661
  for (i = 0; i <= max; i++) {
631
662
  current = current.get(pieces[i]);
663
+
664
+ if (!current || typeof current == 'function') {
665
+ break;
666
+ }
667
+
632
668
  result.push(current);
633
669
 
634
- if (!current || i == max) {
670
+ if (i == max) {
635
671
  break;
636
672
  }
637
673
 
package/lib/core/base.js CHANGED
@@ -345,7 +345,7 @@ Base.setStatic('starts_new_group', false);
345
345
  *
346
346
  * @author Jelle De Loecker <jelle@elevenways.be>
347
347
  * @since 1.1.8
348
- * @version 1.1.8
348
+ * @version 1.2.4
349
349
  *
350
350
  * @type {Conduit}
351
351
  */
@@ -355,16 +355,20 @@ Base.setProperty(function conduit() {
355
355
  return this[CONDUIT];
356
356
  }
357
357
 
358
- let renderer = this.renderer || this.view || this.hawkejs_view,
358
+ let renderer,
359
359
  result;
360
+
361
+ if (Blast.isBrowser || !(this instanceof Classes.Alchemy.Controller)) {
362
+ renderer = this.renderer || this.view || this.hawkejs_view;
363
+ }
360
364
 
361
- if (renderer && renderer.conduit) {
365
+ if (renderer?.conduit) {
362
366
  result = renderer.conduit;
363
- } else if (renderer && renderer.root_renderer && renderer.root_renderer.conduit) {
367
+ } else if (renderer?.root_renderer?.conduit) {
364
368
  result = renderer.root_renderer.conduit;
365
369
  }
366
370
 
367
- if (!conduit && renderer && renderer.server_var) {
371
+ if (!conduit && renderer?.server_var) {
368
372
  result = renderer.server_var('conduit');
369
373
  }
370
374
 
@@ -448,6 +452,39 @@ Base.setMethod(function callOrNext(name, args, next) {
448
452
 
449
453
  // PROTOBLAST START CUT
450
454
 
455
+ /**
456
+ * Get the client implementation of this class
457
+ *
458
+ * @author Jelle De Loecker <jelle@elevenways.be>
459
+ * @since 1.2.4
460
+ * @version 1.2.4
461
+ *
462
+ * @return {Function}
463
+ */
464
+ Base.setStatic(function getClientClass() {
465
+
466
+ if (!this.namespace) {
467
+ return;
468
+ }
469
+
470
+ // It's already a client-side class!
471
+ if (this.namespace.indexOf('.Client.')) {
472
+ return this;
473
+ }
474
+
475
+ // Add the Client part of the namespace
476
+ let namespace = this.namespace.replace('.Alchemy.', '.Alchemy.Client.');
477
+
478
+ // Get the actual namespace object
479
+ namespace = Function.getNamespace(namespace);
480
+
481
+ if (!namespace) {
482
+ return;
483
+ }
484
+
485
+ return namespace[this.name];
486
+ });
487
+
451
488
  /**
452
489
  * Attach a conduit to a certain instance
453
490
  *