alchemymvc 1.3.5 → 1.3.7

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.
@@ -369,7 +369,7 @@ Mongo.setMethod(function _read(model, criteria, callback) {
369
369
  *
370
370
  * @author Jelle De Loecker <jelle@develry.be>
371
371
  * @since 0.2.0
372
- * @version 1.1.0
372
+ * @version 1.3.6
373
373
  */
374
374
  Mongo.setMethod(function _create(model, data, options, callback) {
375
375
 
@@ -394,6 +394,26 @@ Mongo.setMethod(function _create(model, data, options, callback) {
394
394
  return callback(err, result);
395
395
  }
396
396
 
397
+ let write_errors = result.message?.documents?.[0]?.writeErrors;
398
+
399
+ if (write_errors) {
400
+ let violations = new Classes.Alchemy.Error.Validation.Violations();
401
+
402
+ if (write_errors.length) {
403
+ let entry;
404
+
405
+ for (entry of write_errors) {
406
+ let violation = new Classes.Alchemy.Error.Validation.Violation();
407
+ violation.message = entry.errmsg || entry.message || entry.code;
408
+ violations.add(violation);
409
+ }
410
+ } else {
411
+ violations.add(new Error('Unknown database error'));
412
+ }
413
+
414
+ return callback(violations);
415
+ }
416
+
397
417
  callback(null, Object.assign({}, data));
398
418
  });
399
419
  });
@@ -290,16 +290,18 @@ Alchemy.setMethod(function group(name, id, callback) {
290
290
  /**
291
291
  * Print a segment
292
292
  *
293
- * @author Jelle De Loecker <jelle@develry.be>
293
+ * @author Jelle De Loecker <jelle@elevenways.be>
294
294
  * @since 0.5.0
295
- * @version 1.1.5
295
+ * @version 1.3.7
296
296
  *
297
297
  * @param {String|Object} options
298
298
  * @param {Object} data
299
+ *
300
+ * @return {HePlaceholder}
299
301
  */
300
302
  Alchemy.setMethod(function segment(options, data) {
301
303
 
302
- var that = this;
304
+ const that = this;
303
305
 
304
306
  if (typeof options == 'string') {
305
307
  options = {
@@ -307,10 +309,14 @@ Alchemy.setMethod(function segment(options, data) {
307
309
  };
308
310
  }
309
311
 
310
- let start = Date.now();
312
+ let print_placeholder = options.print;
313
+
314
+ if (print_placeholder == null) {
315
+ print_placeholder = true;
316
+ }
311
317
 
312
318
  // Prints a placeholder
313
- this.view.async(function doAsync(next) {
319
+ return this.view.async(function doAsync(next) {
314
320
 
315
321
  var conduit,
316
322
  route;
@@ -354,7 +360,7 @@ Alchemy.setMethod(function segment(options, data) {
354
360
 
355
361
  next(null, block);
356
362
  });
357
- });
363
+ }, {print: print_placeholder});
358
364
  });
359
365
 
360
366
  /**
@@ -1,7 +1,3 @@
1
- const OBJECT = Symbol('Object'),
2
- BACKING = Symbol('Backing'),
3
- MAP = Symbol('Map');
4
-
5
1
  /**
6
2
  * The Backed map class
7
3
  *
@@ -9,249 +5,6 @@ const OBJECT = Symbol('Object'),
9
5
  *
10
6
  * @author Jelle De Loecker <jelle@elevenways.be>
11
7
  * @since 1.2.1
12
- * @version 1.2.1
8
+ * @version 1.3.5
13
9
  */
14
- const Backed = Function.inherits('Alchemy.Base', 'Alchemy.Map', function Backed(backing) {
15
- this.local = new Map();
16
- this.type = null;
17
- this.backing = backing;
18
- });
19
-
20
- /**
21
- * Undry the given value
22
- *
23
- * @author Jelle De Loecker <jelle@elevenways.be>
24
- * @since 1.2.1
25
- * @version 1.2.1
26
- *
27
- * @param {Object} value
28
- *
29
- * @return {EnumValues}
30
- */
31
- Backed.setStatic(function unDry(value, custom_method, whenDone) {
32
- let result = new this(value.backing);
33
- return result;
34
- });
35
-
36
- /**
37
- * Get the size
38
- *
39
- * @author Jelle De Loecker <jelle@elevenways.be>
40
- * @since 1.2.1
41
- * @version 1.2.1
42
- */
43
- Backed.setProperty(function size() {
44
- return this.keys().length;
45
- });
46
-
47
- /**
48
- * Set the backing value
49
- *
50
- * @author Jelle De Loecker <jelle@elevenways.be>
51
- * @since 1.2.1
52
- * @version 1.2.1
53
- */
54
- Backed.enforceProperty(function backing(new_value, old_value) {
55
-
56
- this.backing = null;
57
- this.type = null;
58
-
59
- if (!new_value) {
60
- return;
61
- }
62
-
63
- if (new_value instanceof Backed) {
64
- this.type = BACKING;
65
- } else if (new_value instanceof Map) {
66
- this.type = MAP;
67
- } else if (typeof new_value == 'object') {
68
- this.type = OBJECT;
69
- }
70
-
71
- return new_value;
72
- });
73
-
74
- /**
75
- * Create a (shallow) clone of this backed map.
76
- * Since we don't ever touch the backing itself, we don't have to clone that.
77
- *
78
- * @author Jelle De Loecker <jelle@elevenways.be>
79
- * @since 1.2.1
80
- * @version 1.3.1
81
- *
82
- * @return {Backed}
83
- */
84
- Backed.setMethod(function clone() {
85
- let result = new this.constructor(this.backing);
86
- result.local = new Map(this.local);
87
- return result;
88
- });
89
-
90
- /**
91
- * Create a (shallow) clone of this backed map.
92
- * Since we don't ever touch the backing itself, we don't have to clone that.
93
- *
94
- * @author Jelle De Loecker <jelle@elevenways.be>
95
- * @since 1.2.1
96
- * @version 1.2.1
97
- *
98
- * @return {Backed}
99
- */
100
- Backed.setMethod(function dryClone(wm, custom_method) {
101
- return this.clone();
102
- });
103
-
104
- /**
105
- * Simplify the object for Hawkejs
106
- *
107
- * @author Jelle De Loecker <jelle@elevenways.be>
108
- * @since 1.2.1
109
- * @version 1.2.1
110
- *
111
- * @param {WeakMap} wm
112
- *
113
- * @return {Backed}
114
- */
115
- Backed.setMethod(function toHawkejs(wm) {
116
-
117
- let values = new Map(),
118
- value,
119
- keys = this.keys(),
120
- key;
121
-
122
- for (key of keys) {
123
- value = this.get(key);
124
- values.set(key, JSON.clone(value, 'toHawkejs', wm));
125
- }
126
-
127
- return new this.constructor(values);
128
- });
129
-
130
- /**
131
- * Get all the keys
132
- *
133
- * @author Jelle De Loecker <jelle@elevenways.be>
134
- * @since 1.2.1
135
- * @version 1.2.1
136
- *
137
- * @return {String[]}
138
- */
139
- Backed.setMethod(function keys() {
140
-
141
- let result;
142
-
143
- if (this.type == BACKING) {
144
- result = this.backing.keys();
145
- } else if (this.type == MAP) {
146
- result = [...this.backing.keys()];
147
- } else if (this.type == OBJECT) {
148
- result = Object.keys(this.backing);
149
- } else {
150
- result = [];
151
- }
152
-
153
- if (this.local.size) {
154
- for (let key of this.local.keys()) {
155
- if (result.indexOf(key) == -1) {
156
- result.push(key);
157
- }
158
- }
159
- }
160
-
161
- return result;
162
- });
163
-
164
- /**
165
- * Get a value by it's name
166
- *
167
- * @author Jelle De Loecker <jelle@elevenways.be>
168
- * @since 1.2.1
169
- * @version 1.2.1
170
- *
171
- * @param {String} name
172
- *
173
- * @return {Mixed}
174
- */
175
- Backed.setMethod(function get(name) {
176
-
177
- if (this.local.has(name)) {
178
- return this.local.get(name);
179
- }
180
-
181
- if (!this.type) {
182
- return;
183
- }
184
-
185
- let value;
186
-
187
- if (this.type == BACKING || this.type == MAP) {
188
- value = this.backing.get(name);
189
- } else if (this.type == OBJECT) {
190
- value = this.backing[name];
191
- }
192
-
193
- return value;
194
- });
195
-
196
- /**
197
- * Set a value
198
- *
199
- * @author Jelle De Loecker <jelle@elevenways.be>
200
- * @since 1.2.1
201
- * @version 1.2.1
202
- *
203
- * @param {String} name
204
- * @param {*} value
205
- *
206
- * @return {*}
207
- */
208
- Backed.setMethod(function set(name, value) {
209
- this.local.set(name, value);
210
- return value;
211
- });
212
-
213
- /**
214
- * Dry the object
215
- *
216
- * @author Jelle De Loecker <jelle@elevenways.be>
217
- * @since 1.2.1
218
- * @version 1.2.1
219
- *
220
- * @return {Object}
221
- */
222
- Backed.setMethod(function toDry() {
223
-
224
- let result = {
225
- backing : {}
226
- };
227
-
228
- let value,
229
- key;
230
-
231
- for (key of this.keys()) {
232
- value = this.get(key);
233
- result.backing[key] = value;
234
- }
235
-
236
- return {value: result};
237
- });
238
-
239
- /**
240
- * Iterate over the object
241
- *
242
- * @author Jelle De Loecker <jelle@elevenways.be>
243
- * @since 1.2.1
244
- * @version 1.2.1
245
- *
246
- * @return {Object}
247
- */
248
- Backed.setMethod(Symbol.iterator, function* iterate() {
249
-
250
- let value,
251
- key;
252
-
253
- for (key of this.keys()) {
254
- value = this.get(key);
255
- yield value;
256
- }
257
- });
10
+ const Backed = Function.inherits('Develry.BackedMap', 'Alchemy.Map', 'Backed');
@@ -91,7 +91,7 @@ EnumMap.setMethod(function set(name, value) {
91
91
  *
92
92
  * @author Jelle De Loecker <jelle@elevenways.be>
93
93
  * @since 1.2.1
94
- * @version 1.2.1
94
+ * @version 1.3.6
95
95
  *
96
96
  * @param {WeakMap} wm
97
97
  *
@@ -100,23 +100,45 @@ EnumMap.setMethod(function set(name, value) {
100
100
  EnumMap.setMethod(function toHawkejs(wm) {
101
101
 
102
102
  let result = toHawkejs.super.call(this, wm),
103
- original_value,
104
- cloned_value,
103
+ original_entry,
104
+ cloned_entry,
105
105
  keys = this.keys(),
106
106
  key;
107
107
 
108
108
  for (key of keys) {
109
- original_value = this.get(key);
110
- cloned_value = result.get(key);
109
+ original_entry = this.get(key);
110
+ cloned_entry = result.get(key);
111
+
112
+ if (original_entry.value) {
111
113
 
112
- if (original_value.value) {
114
+ let val = original_entry.value;
113
115
 
114
- if (typeof original_value.value == 'function') {
115
- cloned_value.type = 'function';
116
+ if (typeof val == 'function') {
117
+ cloned_entry.type = 'function';
116
118
  }
117
119
 
118
- if (original_value.value.schema) {
119
- cloned_value.schema = JSON.clone(original_value.value.schema, 'toHawkejs', wm);
120
+ if (val.schema) {
121
+ cloned_entry.schema = JSON.clone(val.schema, 'toHawkejs', wm);
122
+ }
123
+
124
+ let key,
125
+ field_val;
126
+
127
+ for (key in val) {
128
+
129
+ if (key == 'schema') {
130
+ continue;
131
+ }
132
+
133
+ field_val = val[key];
134
+
135
+ if (field_val) {
136
+ const SchemaClass = Blast.isBrowser ? Classes.Alchemy.Client.Schema : Classes.Alchemy.Schema;
137
+
138
+ if (field_val instanceof SchemaClass) {
139
+ cloned_entry[key] = JSON.clone(field_val, 'toHawkejs', wm);
140
+ }
141
+ }
120
142
  }
121
143
  }
122
144
  }
@@ -221,9 +221,9 @@ Router.setMethod(function applyDirective(element, name, options) {
221
221
  /**
222
222
  * Return route name info
223
223
  *
224
- * @author Jelle De Loecker <jelle@develry.be>
224
+ * @author Jelle De Loecker <jelle@elevenways.be>
225
225
  * @since 0.2.0
226
- * @version 1.2.7
226
+ * @version 1.3.7
227
227
  *
228
228
  * @param {String} name
229
229
  * @param {Boolean} socket_route Look in the socket routes
@@ -232,13 +232,12 @@ Router.setMethod(function applyDirective(element, name, options) {
232
232
  */
233
233
  Router.setMethod(function routeConfig(name, socket_route) {
234
234
 
235
- var section,
236
- pieces,
237
- routes,
238
- result,
239
- route,
240
- name,
241
- key;
235
+ if (!name) {
236
+ return null;
237
+ }
238
+
239
+ let section,
240
+ routes;
242
241
 
243
242
  if (socket_route) {
244
243
 
@@ -264,7 +263,7 @@ Router.setMethod(function routeConfig(name, socket_route) {
264
263
  return null;
265
264
  }
266
265
 
267
- pieces = name.split('@');
266
+ let pieces = name.split('@');
268
267
 
269
268
  if (pieces.length == 1) {
270
269
  name = pieces[0];
@@ -273,6 +272,8 @@ Router.setMethod(function routeConfig(name, socket_route) {
273
272
  if (routes.default[name] != null) {
274
273
  section = 'default';
275
274
  } else {
275
+ let key;
276
+
276
277
  for (key in routes) {
277
278
  if (routes[key][name] != null) {
278
279
  section = key
@@ -285,24 +286,19 @@ Router.setMethod(function routeConfig(name, socket_route) {
285
286
  }
286
287
 
287
288
  if (routes[section] != null && routes[section][name] != null) {
288
- route = routes[section][name];
289
+ let route = routes[section][name];
289
290
 
290
- result = {
291
- section : section,
292
- name : name
293
- };
291
+ let result;
294
292
 
295
293
  if (socket_route) {
296
- result.socket_route = true;
294
+ result = {
295
+ section : section,
296
+ name : name,
297
+ socket_route : true,
298
+ };
297
299
  } else {
300
+ result = Object.create(route);
298
301
  result.socket_route = false;
299
- result.paths = route.paths;
300
- result.breadcrumb = route.breadcrumb;
301
- result.has_breadcrumb_assignments = route.has_breadcrumb_assignments;
302
- result.keys = route.keys;
303
- result.methods = route.methods;
304
- result.permission = route.permission;
305
- result.has_permission_assignments = route.has_permission_assignments;
306
302
  }
307
303
 
308
304
  let router_options;
@@ -328,9 +324,9 @@ Router.setMethod(function routeConfig(name, socket_route) {
328
324
  /**
329
325
  * Return the plain url for the given url name & parameters object
330
326
  *
331
- * @author Jelle De Loecker <jelle@develry.be>
327
+ * @author Jelle De Loecker <jelle@elevenways.be>
332
328
  * @since 0.2.0
333
- * @version 1.3.0
329
+ * @version 1.3.7
334
330
  *
335
331
  * @param {String} name
336
332
  * @param {Object} parameters
@@ -391,6 +387,33 @@ Router.setMethod(function routeUrl(name, parameters, options) {
391
387
  return '#url_config_' + name + '_notfound';
392
388
  }
393
389
 
390
+ if (config.param_definitions) {
391
+
392
+ let documents = {},
393
+ definition,
394
+ key;
395
+
396
+ for (key in config.param_definitions) {
397
+ definition = config.param_definitions[key];
398
+
399
+ // If this definition has a specific model name,
400
+ // try to get it from the parameters (and also remove it from there)
401
+ if (definition.type_class_name) {
402
+ if (!documents[definition.type_class_name]) {
403
+ documents[definition.type_class_name] = parameters[definition.type_class_name];
404
+
405
+ if (documents[definition.type_class_name]) {
406
+ delete parameters[definition.type_class_name];
407
+ }
408
+ }
409
+ }
410
+
411
+ if (!parameters[definition.name] && documents[definition.type_class_name]) {
412
+ parameters[definition.name] = documents[definition.type_class_name][definition.type_field_name];
413
+ }
414
+ }
415
+ }
416
+
394
417
  // Remove [brackets]
395
418
  url = url.replace(/\[.*?\]/g, '');
396
419
  url = url.assign(parameters, true, RURL.encodeUriQuery);
@@ -716,7 +739,7 @@ Router.setMethod(function updateLanguageSwitcher(element, variables) {
716
739
  *
717
740
  * @author Jelle De Loecker <jelle@elevenways.be>
718
741
  * @since 1.2.5
719
- * @version 1.3.0
742
+ * @version 1.3.7
720
743
  *
721
744
  * @param {String} prefix The prefix to use
722
745
  * @param {Object} variables
@@ -761,6 +784,8 @@ Router.setMethod(function translateCurrentRoute(prefix, variables) {
761
784
 
762
785
  // Add the get queries
763
786
  if (info.url && info.url.search) {
787
+ let key;
788
+
764
789
  for (key in info.url.query) {
765
790
 
766
791
  if (key == 'hajax' || key == 'h_diversion' || key == 'htop') {
@@ -0,0 +1,116 @@
1
+ let mongo;
2
+
3
+ if (Blast.isNode) {
4
+ mongo = alchemy.use('mongodb');
5
+ }
6
+
7
+ /**
8
+ * The BigInt Field class
9
+ *
10
+ * @constructor
11
+ *
12
+ * @author Jelle De Loecker <jelle@elevenways.be>
13
+ * @since 1.3.6
14
+ * @version 1.3.6
15
+ */
16
+ const BigIntField = Function.inherits('Alchemy.Field.Number', 'BigInt');
17
+
18
+ /**
19
+ * Set the datatype name
20
+ *
21
+ * @author Jelle De Loecker <jelle@elevenways.be>
22
+ * @since 1.3.6
23
+ * @version 1.3.6
24
+ */
25
+ BigIntField.setDatatype('bigint');
26
+
27
+ /**
28
+ * Cast the given value to this field's type
29
+ *
30
+ * @author Jelle De Loecker <jelle@elevenways.be>
31
+ * @since 1.3.6
32
+ * @version 1.3.6
33
+ *
34
+ * @param {Mixed} value
35
+ *
36
+ * @return {BigInt}
37
+ */
38
+ BigIntField.setMethod(function cast(value) {
39
+
40
+ // Allow null
41
+ if (value == null) {
42
+ return value;
43
+ }
44
+
45
+ return BigInt(value);
46
+ });
47
+
48
+ /**
49
+ * Cast the given value to this field's type for search in a db
50
+ *
51
+ * @author Jelle De Loecker <jelle@elevenways.be>
52
+ * @since 1.3.6
53
+ * @version 1.3.6
54
+ *
55
+ * @param {Mixed} value
56
+ * @param {Array} field_paths The path to the field
57
+ *
58
+ * @return {Mixed}
59
+ */
60
+ BigIntField.setMethod(function _castCondition(value, field_paths) {
61
+
62
+ value = this.cast(value);
63
+
64
+ if (mongo) {
65
+ value = mongo.Long.fromBigInt(value);
66
+ }
67
+
68
+ return value;
69
+ });
70
+
71
+ /**
72
+ * Convert the value for the given datasource
73
+ *
74
+ * @author Jelle De Loecker <jelle@elevenways.be>
75
+ * @since 1.3.6
76
+ * @version 1.3.6
77
+ *
78
+ * @param {Mixed} value The field's own value
79
+ * @param {Object} data The main record
80
+ * @param {Datasource} datasource The datasource instance
81
+ *
82
+ * @return {Mixed}
83
+ */
84
+ BigIntField.setMethod(function _toDatasource(value, data, datasource, callback) {
85
+
86
+ value = this.cast(value);
87
+
88
+ if (mongo) {
89
+ value = mongo.Long.fromBigInt(value);
90
+ }
91
+
92
+ callback(null, value);
93
+ });
94
+
95
+ /**
96
+ * Convert from database to app
97
+ *
98
+ * @author Jelle De Loecker <jelle@elevenways.be>
99
+ * @since 1.3.6
100
+ * @version 1.3.6
101
+ *
102
+ * @param {Object} query The original query
103
+ * @param {Object} options The original query options
104
+ * @param {Mixed} value The field value, as stored in the DB
105
+ * @param {Function} callback
106
+ */
107
+ BigIntField.setMethod(function _toApp(query, options, value, callback) {
108
+
109
+ if (mongo && value && value.toBigInt) {
110
+ value = value.toBigInt();
111
+ }
112
+
113
+ value = this.cast(value);
114
+
115
+ callback(null, value);
116
+ });
@@ -33,4 +33,24 @@ BooleanField.setDatatype('boolean');
33
33
  */
34
34
  BooleanField.setMethod(function cast(value) {
35
35
  return Boolean(value);
36
- });
36
+ });
37
+
38
+ /**
39
+ * See if the given vlaue is considered not-empty for this field
40
+ *
41
+ * @author Jelle De Loecker <jelle@elevenways.be>
42
+ * @since 1.3.7
43
+ * @version 1.3.7
44
+ *
45
+ * @param {Mixed} value
46
+ *
47
+ * @return {Boolean}
48
+ */
49
+ BooleanField.setMethod(function valueHasContent(value) {
50
+
51
+ if (value === undefined) {
52
+ return false;
53
+ }
54
+
55
+ return true;
56
+ });
@@ -89,13 +89,11 @@ SchemaField.setProperty(function requires_translating() {
89
89
  /**
90
90
  * Get the subschema of this field
91
91
  *
92
- * @constructor
93
- *
94
- * @author Jelle De Loecker <jelle@develry.be>
92
+ * @author Jelle De Loecker <jelle@elevenways.be>
95
93
  * @since 0.2.0
96
- * @version 1.3.1
94
+ * @version 1.3.7
97
95
  *
98
- * @param {Object} record
96
+ * @param {Object} record This *should* be the schema context (might not be the root)
99
97
  * @param {String} some_path Some path to a field in the wanted schema
100
98
  *
101
99
  * @return {Schema}
@@ -1400,16 +1400,16 @@ Conduit.setMethod(function setResponseUrl(new_url) {
1400
1400
  /**
1401
1401
  * Redirect to another url
1402
1402
  *
1403
- * @author Jelle De Loecker <jelle@develry.be>
1403
+ * @author Jelle De Loecker <jelle@elevenways.be>
1404
1404
  * @since 0.2.0
1405
- * @version 1.2.5
1405
+ * @version 1.3.6
1406
1406
  *
1407
1407
  * @param {Number} status 3xx redirection codes. 302 (temporary redirect) by default
1408
1408
  * @param {String|Object} options Options or url
1409
1409
  */
1410
1410
  Conduit.setMethod(function redirect(status, options) {
1411
1411
 
1412
- var hard_refresh = false,
1412
+ let hard_refresh = false,
1413
1413
  url;
1414
1414
 
1415
1415
  if (typeof status != 'number') {
@@ -1486,6 +1486,20 @@ Conduit.setMethod(function redirect(status, options) {
1486
1486
 
1487
1487
  this.status = status;
1488
1488
 
1489
+ // Make sure the url is an internal one if no hard refresh is requested
1490
+ if (!hard_refresh && alchemy.settings.url) {
1491
+ let rurl = RURL.parse(url);
1492
+
1493
+ // If an explicit hostname is set, this might be an external url
1494
+ if (rurl.hostname) {
1495
+ let base_url = RURL.parse(alchemy.settings.url);
1496
+
1497
+ if (base_url.hostname != rurl.hostname) {
1498
+ hard_refresh = true;
1499
+ }
1500
+ }
1501
+ }
1502
+
1489
1503
  if (hard_refresh && this.headers['x-hawkejs-request']) {
1490
1504
  this.setHeader('x-hawkejs-navigate', url);
1491
1505
 
@@ -2217,7 +2231,7 @@ Conduit.setMethod(function _sendStream(stream, options) {
2217
2231
  *
2218
2232
  * @author Jelle De Loecker <jelle@elevenways.be>
2219
2233
  * @since 0.2.0
2220
- * @version 1.3.1
2234
+ * @version 1.3.6
2221
2235
  *
2222
2236
  * @param {Boolean} create Create a session if none exist
2223
2237
  *
@@ -2231,7 +2245,6 @@ Conduit.setMethod(function getSession(allow_create = true) {
2231
2245
  }
2232
2246
 
2233
2247
  let cookie_name,
2234
- fingerprint,
2235
2248
  session_id,
2236
2249
  session;
2237
2250
 
@@ -2246,30 +2259,11 @@ Conduit.setMethod(function getSession(allow_create = true) {
2246
2259
  session = alchemy.sessions.get(session_id);
2247
2260
  }
2248
2261
 
2249
- // If no session is found, see if we can find one
2250
- // based on the browser fingerprint
2251
- if (!session && this.ip) {
2252
- fingerprint = this.fingerprint;
2253
-
2254
- if (fingerprint) {
2255
- session = alchemy.fingerprints.get(fingerprint);
2256
-
2257
- if (session && session.id) {
2258
- session_id = session.id;
2259
- this.cookie(cookie_name, session_id, {httpOnly: true});
2260
- }
2261
- }
2262
- }
2263
-
2264
2262
  // If no valid session exists, create a new one
2265
2263
  if (!session && allow_create) {
2266
2264
  session = new Classes.Alchemy.ClientSession(this);
2267
2265
  session_id = session.id;
2268
2266
 
2269
- if (fingerprint) {
2270
- alchemy.fingerprints.set(fingerprint, session);
2271
- }
2272
-
2273
2267
  this.cookie(cookie_name, session_id, {httpOnly: true});
2274
2268
 
2275
2269
  alchemy.sessions.set(session_id, session);
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * @constructor
6
6
  *
7
- * @author Jelle De Loecker <jelle@develry.be>
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
8
  * @since 0.2.0
9
- * @version 1.1.0
9
+ * @version 1.3.6
10
10
  *
11
11
  * @param {Schema} schema The schema this field is added to
12
12
  * @param {String} name The name of the field
@@ -22,8 +22,11 @@ Blast.Globals.Field = Function.inherits('Alchemy.Base', 'Alchemy.Field', functio
22
22
  // Also store under parent
23
23
  this.parent_schema = schema;
24
24
 
25
- // Check the indexes
26
- this.checkIndexes();
25
+ if (schema) {
26
+ // Check the indexes
27
+ // @TODO: move this to the `addField` methods?
28
+ this.checkIndexes();
29
+ }
27
30
  });
28
31
 
29
32
  /**
@@ -974,9 +977,9 @@ Field.setMethod(function _getValue(wrapped, done_translation) {
974
977
  /**
975
978
  * See if we need to apply any indexes
976
979
  *
977
- * @author Jelle De Loecker <jelle@develry.be>
980
+ * @author Jelle De Loecker <jelle@elevenways.be>
978
981
  * @since 1.1.0
979
- * @version 1.1.0
982
+ * @version 1.3.6
980
983
  */
981
984
  Field.setMethod(function checkIndexes() {
982
985
 
@@ -997,7 +1000,7 @@ Field.setMethod(function checkIndexes() {
997
1000
  index.sparse = this.options.sparse;
998
1001
  }
999
1002
 
1000
- if (this.options.unique) {
1003
+ if (this.options.unique && this.schema) {
1001
1004
  this.schema.addIndex(this, index);
1002
1005
  } else if (!Object.isEmpty(index)) {
1003
1006
  console.log('@TODO: handle field index:', this, index);
@@ -1075,6 +1078,36 @@ Field.setMethod(function getRules() {
1075
1078
  return result;
1076
1079
  });
1077
1080
 
1081
+ /**
1082
+ * See if the given vlaue is considered not-empty for this field
1083
+ *
1084
+ * @author Jelle De Loecker <jelle@elevenways.be>
1085
+ * @since 1.3.7
1086
+ * @version 1.3.7
1087
+ *
1088
+ * @param {Mixed} value
1089
+ *
1090
+ * @return {Boolean}
1091
+ */
1092
+ Field.setMethod(function valueHasContent(value) {
1093
+
1094
+ if (value == null) {
1095
+ return false;
1096
+ }
1097
+
1098
+ if (value === '') {
1099
+ return false;
1100
+ }
1101
+
1102
+ if (Array.isArray(value)) {
1103
+ if (value.length === 0) {
1104
+ return false;
1105
+ }
1106
+ }
1107
+
1108
+ return true;
1109
+ });
1110
+
1078
1111
  /**
1079
1112
  * Translate the given value
1080
1113
  *
@@ -310,7 +310,7 @@ Model.prepareProperty('datasource', function datasource() {
310
310
  * @type {Object}
311
311
  */
312
312
  Model.prepareProperty('sort', function sort() {
313
- return {created: 1};
313
+ return {[this.primary_key]: -1};
314
314
  });
315
315
 
316
316
  /**
@@ -5,7 +5,7 @@ const PathDefinition = Classes.Alchemy.PathDefinition;
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@elevenways.be>
7
7
  * @since 1.3.0
8
- * @version 1.3.0
8
+ * @version 1.3.7
9
9
  */
10
10
  const PathParamDefinition = Function.inherits('Alchemy.Base', function PathParamDefinition(config) {
11
11
 
@@ -41,6 +41,9 @@ const PathParamDefinition = Function.inherits('Alchemy.Base', function PathParam
41
41
  // The optional type class constructor
42
42
  this.type_class_constructor = null;
43
43
 
44
+ // Is this a model type?
45
+ this.is_model_type = false;
46
+
44
47
  // Has all the config been parsed?
45
48
  this.is_parsed = false;
46
49
  });
@@ -79,12 +82,36 @@ PathParamDefinition.setStatic(function from(input) {
79
82
  return new this(input);
80
83
  });
81
84
 
85
+ /**
86
+ * Get the model constructor
87
+ *
88
+ * @author Jelle De Loecker <jelle@elevenways.be>
89
+ * @since 1.3.7
90
+ * @version 1.3.7
91
+ */
92
+ PathParamDefinition.enforceProperty(function model_constructor(new_value) {
93
+
94
+ if (!this.is_parsed) {
95
+ this.parseTypeDefinition();
96
+ }
97
+
98
+ if (!new_value) {
99
+ const constructor = this.type_class_constructor;
100
+
101
+ if (constructor && this.is_model_type) {
102
+ new_value = constructor;
103
+ }
104
+ }
105
+
106
+ return new_value;
107
+ });
108
+
82
109
  /**
83
110
  * Parse the type definition
84
111
  *
85
112
  * @author Jelle De Loecker <jelle@elevenways.be>
86
113
  * @since 1.3.0
87
- * @version 1.3.0
114
+ * @version 1.3.6
88
115
  */
89
116
  PathParamDefinition.setMethod(function parseTypeDefinition() {
90
117
 
@@ -109,8 +136,21 @@ PathParamDefinition.setMethod(function parseTypeDefinition() {
109
136
  class_name = this.typedef;
110
137
  }
111
138
  } else {
112
- class_name = this.typedef[0];
113
- field_name = this.typedef[1];
139
+
140
+ let class_path = [],
141
+ field_path = [],
142
+ current = class_path;
143
+
144
+ for (let piece of this.typedef) {
145
+ if (piece[0] != piece[0].toUpperCase()) {
146
+ current = field_path;
147
+ }
148
+
149
+ current.push(piece);
150
+ }
151
+
152
+ class_name = class_path.join('.');
153
+ field_name = field_path.join('.');
114
154
  }
115
155
 
116
156
  if (class_name) {
@@ -118,7 +158,14 @@ PathParamDefinition.setMethod(function parseTypeDefinition() {
118
158
  this.type_field_name = field_name;
119
159
 
120
160
  const Model = Classes.Alchemy.Model || Classes.Alchemy.Client.Model;
121
- this.type_class_constructor = Object.path(Model, class_name) || Object.path(Classes.Alchemy, class_name) || Object.path(Classes, class_name);
161
+
162
+ this.type_class_constructor = Object.path(Model, class_name);
163
+
164
+ if (this.type_class_constructor) {
165
+ this.is_model_type = true;
166
+ } else {
167
+ this.type_class_constructor = Object.path(Classes.Alchemy, class_name) || Object.path(Classes, class_name);
168
+ }
122
169
  }
123
170
  });
124
171
 
@@ -129,6 +129,116 @@ Route.setProperty(function has_type_class_checks() {
129
129
  return result;
130
130
  });
131
131
 
132
+ /**
133
+ * Routes with parameters can have schemas
134
+ *
135
+ * @author Jelle De Loecker <jelle@elevenways.be>
136
+ * @since 1.3.7
137
+ * @version 1.3.7
138
+ *
139
+ * @return {Schema}
140
+ */
141
+ Route.enforceProperty(function schema(new_value) {
142
+
143
+ if (new_value) {
144
+ return new_value;
145
+ }
146
+
147
+ if (!this.has_path_assignments) {
148
+ return false;
149
+ }
150
+
151
+ let added_models = {},
152
+ added_fields = {},
153
+ prefix,
154
+ param,
155
+ model_constructor,
156
+ definition;
157
+
158
+ new_value = alchemy.createSchema();
159
+
160
+ for (prefix in this.paths) {
161
+ definition = this.paths[prefix];
162
+
163
+ if (!definition?.param_definitions?.length) {
164
+ continue;
165
+ }
166
+
167
+ for (param of definition.param_definitions) {
168
+ model_constructor = param.model_constructor;
169
+
170
+ if (model_constructor) {
171
+
172
+ if (added_models[model_constructor.name]) {
173
+ continue;
174
+ }
175
+
176
+ if (added_fields[param.name]) {
177
+ continue;
178
+ }
179
+
180
+ added_models[model_constructor.name] = true;
181
+
182
+ try {
183
+ new_value.belongsTo(model_constructor.name);
184
+ } catch (err) {
185
+ // Ignored
186
+ alchemy.distinctProblem('route-schema-' + this.name, 'Route schema error', {error: err});
187
+ }
188
+ } else {
189
+ new_value.addField(param.name, 'String');
190
+ added_fields[param.name] = true;
191
+ }
192
+ }
193
+ }
194
+
195
+ return new_value;
196
+ });
197
+
198
+ /**
199
+ * Get all the param definitions
200
+ *
201
+ * @author Jelle De Loecker <jelle@elevenways.be>
202
+ * @since 1.3.7
203
+ * @version 1.3.7
204
+ *
205
+ * @return {Object}
206
+ */
207
+ Route.enforceProperty(function param_definitions(new_value) {
208
+
209
+ if (new_value) {
210
+ return new_value;
211
+ }
212
+
213
+ let prefix,
214
+ param,
215
+ key,
216
+ definition;
217
+
218
+ new_value = {};
219
+
220
+ for (prefix in this.paths) {
221
+ definition = this.paths[prefix];
222
+
223
+ if (!definition.param_definitions?.length) {
224
+ continue;
225
+ }
226
+
227
+ for (param of definition.param_definitions) {
228
+ key = prefix + '_' + param.name;
229
+
230
+ new_value[key] = {
231
+ name : param.name,
232
+ type_class_name : param.type_class_name,
233
+ type_field_name : param.type_field_name,
234
+ is_model_type : param.is_model_type,
235
+ };
236
+ }
237
+ }
238
+
239
+ return new_value;
240
+ });
241
+
132
242
  /**
133
243
  * Set the breadcrumb for this route
134
244
  *
@@ -1163,7 +1163,7 @@ RouterClass.setMethod(function getOptions(result) {
1163
1163
  *
1164
1164
  * @author Jelle De Loecker <jelle@elevenways.be>
1165
1165
  * @since 0.2.0
1166
- * @version 1.3.0
1166
+ * @version 1.3.7
1167
1167
  *
1168
1168
  * @param {Object} result Optional object to store sectioned results in
1169
1169
  *
@@ -1212,6 +1212,8 @@ RouterClass.setMethod(function getRoutes(result) {
1212
1212
  has_permission_assignments : route.has_permission_assignments || undefined,
1213
1213
  title : route.title,
1214
1214
  visible_location : route.visible_location,
1215
+ schema : route.schema,
1216
+ param_definitions : route.param_definitions,
1215
1217
  };
1216
1218
  }
1217
1219
 
@@ -353,11 +353,13 @@ Sitemap.setMethod(function addRouteWithParameters(route, config, parameters, for
353
353
  *
354
354
  * @author Jelle De Loecker <jelle@elevenways.be>
355
355
  * @since 1.3.4
356
- * @version 1.3.4
356
+ * @version 1.3.6
357
+ *
358
+ * @param {String} prefix
357
359
  *
358
360
  * @return {Hawkejs.Builder}
359
361
  */
360
- Sitemap.setMethod(function getXmlBuilder() {
362
+ Sitemap.setMethod(function getXmlBuilder(prefix) {
361
363
 
362
364
  let xml = Classes.Hawkejs.Builder.create({xml: true}),
363
365
  urlset = xml.ele('urlset');
@@ -367,13 +369,18 @@ Sitemap.setMethod(function getXmlBuilder() {
367
369
  .att('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd')
368
370
  .att('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
369
371
 
370
- let categories = this.getCategories();
372
+ let categories = this.getCategories(prefix);
371
373
 
372
374
  for (let category of categories) {
373
- for (let info of category) {
375
+
376
+ if (!category.pages) {
377
+ continue;
378
+ }
379
+
380
+ for (let info of category.pages) {
374
381
  let url = urlset.ele(url);
375
382
 
376
- url.ele('loc').txt(info.url);
383
+ url.ele('loc').txt(''+info.url);
377
384
 
378
385
  if (info.lastmod) {
379
386
  url.ele('lastmod').txt(info.lastmod);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.3.5",
4
+ "version": "1.3.7",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -31,7 +31,7 @@
31
31
  "mongodb" : "~3.6.6",
32
32
  "ncp" : "~2.0.0",
33
33
  "postcss" : "~8.4.6",
34
- "protoblast" : "~0.8.3",
34
+ "protoblast" : "~0.8.5",
35
35
  "semver" : "~7.3.5",
36
36
  "socket.io" : "~4.6.0",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",