alchemymvc 1.3.16 → 1.3.17

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.
@@ -1277,6 +1277,7 @@ Document.setMethod(function hasChanged(name) {
1277
1277
 
1278
1278
  for (key in this.$attributes.original_record) {
1279
1279
  if (!Object.alike(this.$attributes.original_record[key], this[key])) {
1280
+ // @TODO: some special fields always end up being different
1280
1281
  result = true;
1281
1282
  break;
1282
1283
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The Alchemy Task History model:
3
+ * keep track of all the tasks that have been run
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 0.5.0
9
+ * @version 0.5.0
10
+ */
11
+ const AlchemyTaskHistory = Function.inherits('Alchemy.Model.App', 'AlchemyTaskHistory');
12
+
13
+ /**
14
+ * Constitute the class wide schema
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 0.5.0
18
+ * @version 1.3.17
19
+ */
20
+ AlchemyTaskHistory.constitute(function addTaskFields() {
21
+
22
+ this.belongsTo('AlchemyTask', {
23
+ description : 'The original AlchemyTask document it belonged to',
24
+ });
25
+
26
+ // The timestamp this was scheduled for
27
+ this.addField('scheduled_at', 'Date', {
28
+ description : 'The original date this was scheduled for',
29
+ });
30
+
31
+ this.addField('process_id', 'Integer', {
32
+ description : 'The process ID of the task',
33
+ });
34
+
35
+ // The type of Task that is running
36
+ this.addField('type', 'Enum', {
37
+ values: Classes.Alchemy.Task.Task.getLiveDescendantsMap(),
38
+ });
39
+
40
+ // The payload/settings of the task
41
+ this.addField('settings', 'Schema', {
42
+ description : 'The settings of the task at the time it ran',
43
+ schema: 'type'
44
+ });
45
+
46
+ this.addField('had_error', 'Boolean', {
47
+ description : 'Did this task run into an error?'
48
+ });
49
+
50
+ this.addField('error_message', 'String', {
51
+ description : 'The main error message'
52
+ });
53
+
54
+ this.addField('error_stack', 'Text', {
55
+ description : 'The error stack trace'
56
+ });
57
+
58
+ this.addField('is_running', 'Boolean', {
59
+ description : 'Is this task still running?',
60
+ default : false,
61
+ });
62
+
63
+ this.addField('started_at', 'Datetime', {
64
+ description : 'The datetime this task actually started running',
65
+ });
66
+
67
+ this.addField('ended_at', 'Datetime', {
68
+ description : 'The datetime this task actually ended',
69
+ });
70
+
71
+ this.addIndex('type');
72
+ this.addIndex('is_running');
73
+ });
74
+
75
+ /**
76
+ * Configure the default chimera fieldsets
77
+ *
78
+ * @author Jelle De Loecker <jelle@elevenways.be>
79
+ * @since 1.3.17
80
+ * @version 1.3.17
81
+ */
82
+ AlchemyTaskHistory.constitute(function chimeraConfig() {
83
+
84
+ if (!this.chimera) {
85
+ return;
86
+ }
87
+
88
+ // Get the list group
89
+ let list = this.chimera.getActionFields('list');
90
+
91
+ list.addField('created');
92
+ list.addField('type');
93
+ list.addField('process_id');
94
+ list.addField('scheduled_at');
95
+ list.addField('started_at');
96
+ list.addField('ended_at');
97
+ list.addField('had_error');
98
+
99
+ // Get the edit group
100
+ let edit = this.chimera.getActionFields('edit');
101
+
102
+ edit.addField('type');
103
+ edit.addField('settings');
104
+ edit.addField('had_error');
105
+ edit.addField('error_message');
106
+ edit.addField('error_stack');
107
+ edit.addField('started_at');
108
+ edit.addField('ended_at');
109
+ });
@@ -1,34 +1,157 @@
1
- var all_task_types = alchemy.getClassGroup('task');
2
-
3
1
  /**
4
- * The Alchemy Task Model class
2
+ * The Alchemy Task model:
3
+ *
5
4
  *
6
5
  * @constructor
7
6
  *
8
- * @author Jelle De Loecker <jelle@develry.be>
9
- * @since 0.5.0
10
- * @version 0.5.0
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 1.3.17
9
+ * @version 1.3.17
10
+ */
11
+ const AlchemyTask = Function.inherits('Alchemy.Model.App', 'AlchemyTask');
12
+
13
+ /**
14
+ * Constitute the class wide schema
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 1.3.17
18
+ * @version 1.3.17
11
19
  */
12
- var AlchemyTask = Function.inherits('Alchemy.Model.App', function AlchemyTask(conduit, options) {
20
+ AlchemyTask.constitute(function addTaskFields() {
21
+
22
+ this.addField('title', 'String', {
23
+ description : 'The title of the task',
24
+ });
25
+
26
+ this.addField('type', 'Enum', {
27
+ values : Classes.Alchemy.Task.Task.getLiveDescendantsMap(),
28
+ description : 'The type of task to run',
29
+ });
30
+
31
+ this.addField('settings', 'Schema', {
32
+ description : 'The settings to use for running the task',
33
+ schema: 'type'
34
+ });
13
35
 
14
- var that = this;
36
+ this.addField('enabled', 'Boolean', {
37
+ description : 'Is this task enabled?',
38
+ default : true,
39
+ });
15
40
 
16
- AlchemyTask.super.call(this, conduit, options);
41
+ this.addField('frequency', 'String', {
42
+ description : 'The frequency this task should run at (CRON syntax)',
43
+ });
44
+
45
+ this.addField('comment', 'Text', {
46
+ description : 'A comment about this task',
47
+ });
48
+
49
+ this.addField('schedule_type', 'Enum', {
50
+ values: {
51
+ user : 'User',
52
+ system_forced : 'System (forced)',
53
+ system_fallback : 'System (fallback)',
54
+ },
55
+ default: 'user',
56
+ });
57
+
58
+ this.addField('forced_schedule_checksum', 'String', {
59
+ description : 'Checksum of the cron syntax and settings, set only for tasks originating from forced schedules',
60
+ });
61
+
62
+ this.addIndex('forced_schedule_checksum', {
63
+ unique : true,
64
+ sparse : true,
65
+ });
17
66
  });
18
67
 
19
68
  /**
20
- * Constitute the class wide schema
69
+ * Configure the default chimera fieldsets
21
70
  *
22
- * @author Jelle De Loecker <jelle@develry.be>
23
- * @since 0.5.0
24
- * @version 0.5.0
71
+ * @author Jelle De Loecker <jelle@elevenways.be>
72
+ * @since 1.3.17
73
+ * @version 1.3.17
25
74
  */
26
- AlchemyTask.constitute(function addTaskFields() {
75
+ AlchemyTask.constitute(function chimeraConfig() {
76
+
77
+ if (!this.chimera) {
78
+ return;
79
+ }
80
+
81
+ // Get the list group
82
+ let list = this.chimera.getActionFields('list');
83
+
84
+ list.addField('title');
85
+ list.addField('type');
86
+ list.addField('enabled');
87
+ list.addField('frequency');
88
+ list.addField('schedule_type');
89
+
90
+ // Get the edit group
91
+ let edit = this.chimera.getActionFields('edit');
92
+
93
+ edit.addField('title');
94
+ edit.addField('frequency')
95
+ edit.addField('enabled');
96
+ edit.addField('type');
97
+ edit.addField('settings');
98
+ edit.addField('comment');
99
+ });
100
+
101
+ /**
102
+ * Do something before saving the record
103
+ *
104
+ * @author Jelle De Loecker <jelle@elevenways.be>
105
+ * @since 1.3.17
106
+ * @version 1.3.17
107
+ *
108
+ * @param {Document.AlchemyTask} doc
109
+ */
110
+ AlchemyTask.setMethod(function beforeSave(doc) {
111
+
112
+ if (!doc.schedule_type) {
113
+ doc.schedule_type = 'user';
114
+ }
115
+
116
+ if (doc.enabled == null) {
117
+ doc.enabled = false;
118
+ }
119
+
120
+ if (!doc.title) {
121
+ let title = '';
122
+
123
+ if (doc.schedule_type == 'system_forced') {
124
+ title += 'System forced: ';
125
+ }
126
+
127
+ if (doc.schedule_type == 'system_fallback') {
128
+ title += 'System fallback: ';
129
+ }
130
+
131
+ title += doc.type;
132
+ doc.title = title;
133
+ }
134
+ });
135
+
136
+ /**
137
+ * Update the schedules after saving
138
+ *
139
+ * @author Jelle De Loecker <jelle@elevenways.be>
140
+ * @since 1.3.17
141
+ * @version 1.3.17
142
+ *
143
+ * @param {Object} main
144
+ * @param {Object} info
145
+ */
146
+ AlchemyTask.setMethod(function afterSave(main, info) {
27
147
 
28
- // The type of Task that is running
29
- this.addField('type', 'Enum', {values: all_task_types});
148
+ if (!main.type) {
149
+ return;
150
+ }
30
151
 
31
- // When the task ended
32
- this.addField('ended', 'Datetime');
152
+ if (!alchemy.task_service.has_loaded) {
153
+ return;
154
+ }
33
155
 
156
+ alchemy.task_service.rescheduleTasksOfType(main.type);
34
157
  });
package/lib/bootstrap.js CHANGED
@@ -312,6 +312,9 @@ Alchemy.setMethod(function start(options, callback) {
312
312
  // Make sure Blast has executed everything that's still waiting
313
313
  Blast.doLoaded();
314
314
 
315
+ // Call the `afterStart` method
316
+ this.ready(() => this.afterStart());
317
+
315
318
  // Schedule the callback
316
319
  return this.ready(callback);
317
320
  });
@@ -490,9 +490,9 @@ Conduit.setMethod(function time() {
490
490
  /**
491
491
  * Parse the request, get information from the url
492
492
  *
493
- * @author Jelle De Loecker <jelle@develry.be>
493
+ * @author Jelle De Loecker <jelle@elevenways.be>
494
494
  * @since 0.2.0
495
- * @version 1.2.5
495
+ * @version 1.3.17
496
496
  *
497
497
  * @param {IncomingMessage} req
498
498
  * @param {ServerResponse} res
@@ -555,6 +555,11 @@ Conduit.setMethod(async function parseRequest() {
555
555
  // Set the host
556
556
  this.url.hostname = this.headers.host;
557
557
 
558
+ // If no URL was set, use the first requests URL (without the path)
559
+ if (!alchemy.settings.url && this.headers.host) {
560
+ alchemy.settings.url = this.url+'';
561
+ }
562
+
558
563
  let path = this.path;
559
564
 
560
565
  if (this.prefix) {
@@ -569,9 +574,9 @@ Conduit.setMethod(async function parseRequest() {
569
574
  /**
570
575
  * Parse the headers for shortcuts
571
576
  *
572
- * @author Jelle De Loecker <jelle@develry.be>
577
+ * @author Jelle De Loecker <jelle@elevenways.be>
573
578
  * @since 0.2.0
574
- * @version 0.2.0
579
+ * @version 1.3.17
575
580
  */
576
581
  Conduit.setMethod(function parseShortcuts() {
577
582
 
@@ -597,6 +602,10 @@ Conduit.setMethod(function parseShortcuts() {
597
602
  this.ajax = headers['x-requested-with'] === 'XMLHttpRequest';
598
603
  }
599
604
 
605
+ // Is this request coming from a webview?
606
+ if (this.in_webview == null) {
607
+ this.in_webview = headers['x-protoblast-webview'] === 'true';
608
+ }
600
609
  });
601
610
 
602
611
  /**
@@ -1161,6 +1170,8 @@ Conduit.setMethod(async function callMiddleware() {
1161
1170
  middleDebug.mark('Preparing viewrender');
1162
1171
  }
1163
1172
 
1173
+ // An action will be called,
1174
+ // so we can already prepare the viewrender now.
1164
1175
  that.prepareViewRender();
1165
1176
 
1166
1177
  if (middleDebug) {
@@ -1184,10 +1195,14 @@ Conduit.setMethod(async function callMiddleware() {
1184
1195
  *
1185
1196
  * @author Jelle De Loecker <jelle@develry.be>
1186
1197
  * @since 0.2.0
1187
- * @version 1.1.1
1198
+ * @version 1.3.17
1188
1199
  */
1189
1200
  Conduit.setMethod(function prepareViewRender() {
1190
1201
 
1202
+ if (this.renderer.conduit) {
1203
+ return;
1204
+ }
1205
+
1191
1206
  // Add a link to this conduit
1192
1207
  this.renderer.conduit = this;
1193
1208
  this.renderer.server_var('conduit', this);
@@ -1410,7 +1425,7 @@ Conduit.setMethod(function setResponseUrl(new_url) {
1410
1425
  *
1411
1426
  * @author Jelle De Loecker <jelle@elevenways.be>
1412
1427
  * @since 0.2.0
1413
- * @version 1.3.6
1428
+ * @version 1.3.17
1414
1429
  *
1415
1430
  * @param {Number} status 3xx redirection codes. 302 (temporary redirect) by default
1416
1431
  * @param {String|Object} options Options or url
@@ -1508,13 +1523,18 @@ Conduit.setMethod(function redirect(status, options) {
1508
1523
  }
1509
1524
  }
1510
1525
 
1511
- if (hard_refresh && this.headers['x-hawkejs-request']) {
1512
- this.setHeader('x-hawkejs-navigate', url);
1526
+ if (options?.popup) {
1527
+ this.setHeader('x-hawkejs-popup', options.popup);
1513
1528
 
1514
- if (options && options.popup) {
1515
- this.setHeader('x-hawkejs-popup', options.popup);
1529
+ if (options.popup_url) {
1530
+ this.setHeader('x-hawkejs-popup-url', options.popup_url);
1516
1531
  }
1517
1532
 
1533
+ hard_refresh = true;
1534
+ }
1535
+
1536
+ if (hard_refresh && this.headers['x-hawkejs-request']) {
1537
+ this.setHeader('x-hawkejs-navigate', url);
1518
1538
  } else {
1519
1539
  this.setHeader('Location', url);
1520
1540
  }
@@ -1921,11 +1941,11 @@ Conduit.setMethod(function createScene() {
1921
1941
  *
1922
1942
  * @author Jelle De Loecker <jelle@develry.be>
1923
1943
  * @since 0.2.0
1924
- * @version 1.1.0
1944
+ * @version 1.3.17
1925
1945
  */
1926
1946
  Conduit.setMethod(function render(template_name, options, callback) {
1927
1947
 
1928
- var that = this,
1948
+ let that = this,
1929
1949
  templates;
1930
1950
 
1931
1951
  if (typeof options == 'function') {
@@ -1935,6 +1955,10 @@ Conduit.setMethod(function render(template_name, options, callback) {
1935
1955
  options = {};
1936
1956
  }
1937
1957
 
1958
+ // Make sure the viewrender is prepared
1959
+ // (Fallback)
1960
+ this.prepareViewRender();
1961
+
1938
1962
  if (template_name) {
1939
1963
  templates = [template_name];
1940
1964
  }
@@ -1246,6 +1246,30 @@ Model.setMethod(function nukeCache(associated, parent) {
1246
1246
  }
1247
1247
  });
1248
1248
 
1249
+ /**
1250
+ * Perform a MongoDB pipeline
1251
+ *
1252
+ * @author Jelle De Loecker <jelle@elevenways.be>
1253
+ * @since 1.3.17
1254
+ * @version 1.3.17
1255
+ *
1256
+ * @param {Array} pipeline
1257
+ *
1258
+ * @return {Promise}
1259
+ */
1260
+ Model.setMethod(async function executeMongoPipeline(pipeline) {
1261
+
1262
+ if (typeof this.datasource.collection != 'function') {
1263
+ throw new Error('The `' + this.model_name + '` model does not seem to use MongoDB, unable to perform pipeline');
1264
+ }
1265
+
1266
+ let collection = await this.datasource.collection(this.table);
1267
+ let cursor = await collection.aggregate(pipeline);
1268
+ let result = await cursor.toArray();
1269
+
1270
+ return result;
1271
+ });
1272
+
1249
1273
  /**
1250
1274
  * Delete the given record id
1251
1275
  *
@@ -892,7 +892,7 @@ Schema.setMethod(function getFieldNames() {
892
892
  *
893
893
  * @author Jelle De Loecker <jelle@develry.be>
894
894
  * @since 0.2.0
895
- * @version 1.3.0
895
+ * @version 1.3.17
896
896
  *
897
897
  * @param {String|FieldType} _field
898
898
  * @param {Object} options
@@ -901,24 +901,19 @@ Schema.setMethod(function getFieldNames() {
901
901
  */
902
902
  Schema.setMethod(function addIndex(_field, _options) {
903
903
 
904
- var that = this,
905
- datasource,
906
- options,
907
- order,
908
- field,
909
- path;
910
-
911
904
  // `Schema` is the `Client.Schema` class in this scope
912
905
  if (Blast.isNode && this.constructor == Schema) {
913
906
  return;
914
907
  }
915
908
 
916
- field = this.getField(_field);
909
+ let field = this.getField(_field);
917
910
 
918
911
  if (!field) {
919
912
  throw new Error('Could not find field "' + _field + '"');
920
913
  }
921
914
 
915
+ let options;
916
+
922
917
  if (typeof _options === 'string') {
923
918
  options = {};
924
919
  options[_options] = true;
@@ -961,6 +956,8 @@ Schema.setMethod(function addIndex(_field, _options) {
961
956
  this.has_alternates++;
962
957
  }
963
958
 
959
+ const that = this;
960
+
964
961
  that.getDatasource().done(function gotDs(err, datasource) {
965
962
 
966
963
  if (err) {
@@ -977,7 +974,7 @@ Schema.setMethod(function addIndex(_field, _options) {
977
974
  return alchemy.printLog('error', ['Unable to ensure index on this datasource', options.name], {err: new Error()});
978
975
  }
979
976
 
980
- path = field.path;
977
+ let path = field.path;
981
978
 
982
979
  if (options.db_property) {
983
980
  path += '.' + options.db_property;
@@ -990,7 +987,7 @@ Schema.setMethod(function addIndex(_field, _options) {
990
987
  datasource.ensureIndex(that.model_class, that.indexes[options.name], function ensuredIndex(err, result) {
991
988
 
992
989
  if (err) {
993
- alchemy.printLog('error', ['Error ensuring index', options.name], {err: err});
990
+ alchemy.printLog('error', ['Error ensuring index', options.name, 'in model', that.model_name], {err: err});
994
991
  }
995
992
  });
996
993
  });
@@ -63,7 +63,7 @@ var Session = Function.inherits('Alchemy.Base', function ClientSession(conduit)
63
63
  *
64
64
  * @author Jelle De Loecker <jelle@develry.be>
65
65
  * @since 1.1.0
66
- * @version 1.1.0
66
+ * @version 1.3.17
67
67
  *
68
68
  * @type {Number}
69
69
  */
@@ -75,6 +75,10 @@ Session.enforceProperty(function request_count(amount) {
75
75
 
76
76
  this.last_activity_date = new Date();
77
77
 
78
+ if (this.menu_item) {
79
+ this.menu_item.setWeight(this.last_activity_date.getTime());
80
+ }
81
+
78
82
  return amount;
79
83
  });
80
84
 
@@ -167,7 +171,7 @@ Session.setMethod(function hasAlreadyQueued() {
167
171
  *
168
172
  * @author Jelle De Loecker <jelle@develry.be>
169
173
  * @since 0.2.0
170
- * @version 0.4.0
174
+ * @version 1.3.17
171
175
  *
172
176
  * @param {Conduit} conduit The conduit to postpone
173
177
  *
@@ -187,7 +191,14 @@ Session.setMethod(function createMenuItem(conduit) {
187
191
 
188
192
  browser += ': ' + conduit.ip;
189
193
 
190
- this.menu_item = alchemy.Janeway.session_menu.addItem(browser);
194
+ let options = {
195
+ title : browser,
196
+ weight : this.last_activity_date.getTime(),
197
+ };
198
+
199
+ this.menu_item = alchemy.Janeway.session_menu.addItem(options, () => {
200
+ console.log('Clicked on session', this, this.id);
201
+ });
191
202
  });
192
203
 
193
204
  /**