apostrophe 2.223.1 → 2.225.0-alpha
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/CHANGELOG.md +12 -0
- package/index.js +2 -0
- package/lib/modules/apostrophe-pages/index.js +10 -6
- package/lib/modules/apostrophe-pages/lib/api.js +67 -15
- package/package.json +1 -1
- package/test/base-url-env-var.js +45 -0
- package/test/pages.js +24 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## UNRELEASED
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Accepts `APOS_BASE_URL` environment variable as an override of the global `baseUrl` option. Useful in devops.
|
|
8
|
+
|
|
9
|
+
## 2.224.0 (2023-02-01)
|
|
10
|
+
|
|
11
|
+
### Adds
|
|
12
|
+
|
|
13
|
+
* If about to 404, redirect uppercase URLs automatically to their lowercase version - can be disabled with `redirectFailedUpperCaseUrls: false` in `apostrophe-pages/index.js` options.
|
|
14
|
+
|
|
3
15
|
## 2.223.1 (2022-12-21)
|
|
4
16
|
|
|
5
17
|
### Fixes
|
package/index.js
CHANGED
|
@@ -335,6 +335,8 @@ module.exports = function(options) {
|
|
|
335
335
|
throw "Specify the `shortName` option and set it to the name of your project's repository or folder";
|
|
336
336
|
}
|
|
337
337
|
self.title = self.options.title;
|
|
338
|
+
// For devops purposes
|
|
339
|
+
self.options.baseUrl = process.env.APOS_BASE_URL || self.options.baseUrl;
|
|
338
340
|
self.baseUrl = self.options.baseUrl;
|
|
339
341
|
self.prefix = self.options.prefix || '';
|
|
340
342
|
}
|
|
@@ -255,6 +255,8 @@ module.exports = {
|
|
|
255
255
|
|
|
256
256
|
alias: 'pages',
|
|
257
257
|
|
|
258
|
+
redirectFailedUpperCaseUrls: true,
|
|
259
|
+
|
|
258
260
|
types: [
|
|
259
261
|
{
|
|
260
262
|
// So that the minimum parked pages don't result in an error when home has no manager. -Tom
|
|
@@ -324,12 +326,14 @@ module.exports = {
|
|
|
324
326
|
name: 'trash',
|
|
325
327
|
label: 'Trash'
|
|
326
328
|
}
|
|
327
|
-
].concat(options.apos.docs.trashInSchema
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
].concat(options.apos.docs.trashInSchema
|
|
330
|
+
? [
|
|
331
|
+
{
|
|
332
|
+
name: 'rescue',
|
|
333
|
+
label: 'Rescue'
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
: []).concat([
|
|
333
337
|
{
|
|
334
338
|
name: 'publish',
|
|
335
339
|
label: 'Publish'
|
|
@@ -429,7 +429,13 @@ module.exports = function(self, options) {
|
|
|
429
429
|
.trash(null)
|
|
430
430
|
.published(null)
|
|
431
431
|
.areas(false)
|
|
432
|
-
.ancestors(_.assign({
|
|
432
|
+
.ancestors(_.assign({
|
|
433
|
+
depth: 1,
|
|
434
|
+
trash: null,
|
|
435
|
+
published: null,
|
|
436
|
+
areas: false,
|
|
437
|
+
permission: false
|
|
438
|
+
}, options.filters || {}))
|
|
433
439
|
.applyFilters(options.filters || {})
|
|
434
440
|
.toObject(function(err, page) {
|
|
435
441
|
if (err) {
|
|
@@ -460,7 +466,13 @@ module.exports = function(self, options) {
|
|
|
460
466
|
.trash(null)
|
|
461
467
|
.published(null)
|
|
462
468
|
.areas(false)
|
|
463
|
-
.ancestors(_.assign({
|
|
469
|
+
.ancestors(_.assign({
|
|
470
|
+
depth: 1,
|
|
471
|
+
trash: null,
|
|
472
|
+
published: null,
|
|
473
|
+
areas: false,
|
|
474
|
+
permission: false
|
|
475
|
+
}, options.filters || {}))
|
|
464
476
|
.applyFilters(options.filters || {})
|
|
465
477
|
.toObject(function(err, page) {
|
|
466
478
|
if (err) {
|
|
@@ -516,7 +528,11 @@ module.exports = function(self, options) {
|
|
|
516
528
|
function nudgeNewPeers(callback) {
|
|
517
529
|
// Nudge down the pages that should now follow us
|
|
518
530
|
// Always remember multi: true
|
|
519
|
-
self.apos.docs.db.update(mergeCriteria({
|
|
531
|
+
self.apos.docs.db.update(mergeCriteria({
|
|
532
|
+
path: self.matchDescendants(parent),
|
|
533
|
+
level: parent.level + 1,
|
|
534
|
+
rank: { $gte: rank }
|
|
535
|
+
}), { $inc: { rank: 1 } }, { multi: true }, function(err, count) {
|
|
520
536
|
return callback(err);
|
|
521
537
|
});
|
|
522
538
|
}
|
|
@@ -738,7 +754,12 @@ module.exports = function(self, options) {
|
|
|
738
754
|
[
|
|
739
755
|
function getPage(cb) {
|
|
740
756
|
// check permissions and load page to trash/untrash
|
|
741
|
-
return self.find(req, { _id: _id }).permission('edit').trash(null).ancestors({
|
|
757
|
+
return self.find(req, { _id: _id }).permission('edit').trash(null).ancestors({
|
|
758
|
+
depth: 1,
|
|
759
|
+
published: null,
|
|
760
|
+
trash: null,
|
|
761
|
+
areas: false
|
|
762
|
+
}).toObject((err, _page) => {
|
|
742
763
|
page = _page;
|
|
743
764
|
tree.push(page);
|
|
744
765
|
parent = page._ancestors[0];
|
|
@@ -876,14 +897,17 @@ module.exports = function(self, options) {
|
|
|
876
897
|
var parent;
|
|
877
898
|
var changed = [];
|
|
878
899
|
|
|
879
|
-
return async.series([findTrash, findPage, movePage], function(err) {
|
|
900
|
+
return async.series([ findTrash, findPage, movePage ], function(err) {
|
|
880
901
|
return callback(err, parent && parent.slug, changed);
|
|
881
902
|
});
|
|
882
903
|
|
|
883
904
|
function findTrash(callback) {
|
|
884
905
|
// Always only one trash page at level 1, so we don't have
|
|
885
906
|
// to hardcode the slug
|
|
886
|
-
return self.find(req, {
|
|
907
|
+
return self.find(req, {
|
|
908
|
+
trash: true,
|
|
909
|
+
level: 1
|
|
910
|
+
})
|
|
887
911
|
.permission(false)
|
|
888
912
|
.published(null)
|
|
889
913
|
.trash(null)
|
|
@@ -899,7 +923,12 @@ module.exports = function(self, options) {
|
|
|
899
923
|
|
|
900
924
|
function findPage(callback) {
|
|
901
925
|
// Also checks permissions
|
|
902
|
-
return self.find(req, { _id: _id }).permission('edit').ancestors({
|
|
926
|
+
return self.find(req, { _id: _id }).permission('edit').ancestors({
|
|
927
|
+
depth: 1,
|
|
928
|
+
published: null,
|
|
929
|
+
trash: null,
|
|
930
|
+
areas: false
|
|
931
|
+
}).toObject(function(err, _page) {
|
|
903
932
|
if (err || (!_page)) {
|
|
904
933
|
return callback('Page not found');
|
|
905
934
|
}
|
|
@@ -943,7 +972,12 @@ module.exports = function(self, options) {
|
|
|
943
972
|
|
|
944
973
|
function findPage(callback) {
|
|
945
974
|
// Also checks permissions
|
|
946
|
-
return self.find(req, { _id: _id }).permission('publish').trash(true).ancestors({
|
|
975
|
+
return self.find(req, { _id: _id }).permission('publish').trash(true).ancestors({
|
|
976
|
+
depth: 1,
|
|
977
|
+
published: null,
|
|
978
|
+
trash: null,
|
|
979
|
+
areas: false
|
|
980
|
+
}).toObject(function(err, _page) {
|
|
947
981
|
if (err || (!_page)) {
|
|
948
982
|
return callback('Page not found');
|
|
949
983
|
}
|
|
@@ -1134,6 +1168,12 @@ module.exports = function(self, options) {
|
|
|
1134
1168
|
if (self.isFound(req)) {
|
|
1135
1169
|
return callback(null);
|
|
1136
1170
|
}
|
|
1171
|
+
|
|
1172
|
+
if (options.redirectFailedUpperCaseUrls && /[A-Z]/.test(req.path)) {
|
|
1173
|
+
req.redirect = self.apos.urls.build(req.path.toLowerCase(), req.query);
|
|
1174
|
+
return callback(null);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1137
1177
|
req.data.suggestedSearch = self.apos.utils.slugify(req.url, { separator: ' ' });
|
|
1138
1178
|
req.notFound = true;
|
|
1139
1179
|
req.res.statusCode = 404;
|
|
@@ -1141,7 +1181,7 @@ module.exports = function(self, options) {
|
|
|
1141
1181
|
// Give the browser a chance to do something interesting with a 404.
|
|
1142
1182
|
// This is often a better idea than doing a heavy fallback search
|
|
1143
1183
|
// server side, because those are triggered heavily by bots
|
|
1144
|
-
req.browserCall(
|
|
1184
|
+
req.browserCall('apos.emit(\'notfound\', ?)', {
|
|
1145
1185
|
suggestedSearch: req.data.suggestedSearch,
|
|
1146
1186
|
url: req.url
|
|
1147
1187
|
});
|
|
@@ -1421,7 +1461,10 @@ module.exports = function(self, options) {
|
|
|
1421
1461
|
|
|
1422
1462
|
self.ensurePathIndex = function(callback) {
|
|
1423
1463
|
var params = self.getPathIndexParams();
|
|
1424
|
-
return self.apos.docs.db.ensureIndex(params, {
|
|
1464
|
+
return self.apos.docs.db.ensureIndex(params, {
|
|
1465
|
+
unique: true,
|
|
1466
|
+
sparse: true
|
|
1467
|
+
}, callback);
|
|
1425
1468
|
};
|
|
1426
1469
|
|
|
1427
1470
|
self.getPathIndexParams = function() {
|
|
@@ -1502,8 +1545,14 @@ module.exports = function(self, options) {
|
|
|
1502
1545
|
var matchParentPathPrefix = new RegExp('^' + self.apos.utils.regExpQuote(originalPath + '/'));
|
|
1503
1546
|
var matchParentSlugPrefix = new RegExp('^' + self.apos.utils.regExpQuote(originalSlug + '/'));
|
|
1504
1547
|
var done = false;
|
|
1505
|
-
var cursor = self.apos.docs.db.findWithProjection(mergeCriteria({ path: matchParentPathPrefix }), {
|
|
1506
|
-
|
|
1548
|
+
var cursor = self.apos.docs.db.findWithProjection(mergeCriteria({ path: matchParentPathPrefix }), {
|
|
1549
|
+
slug: 1,
|
|
1550
|
+
path: 1,
|
|
1551
|
+
level: 1
|
|
1552
|
+
});
|
|
1553
|
+
return async.whilst(function() {
|
|
1554
|
+
return !done;
|
|
1555
|
+
}, function(callback) {
|
|
1507
1556
|
return cursor.nextObject(function(err, desc) {
|
|
1508
1557
|
if (err) {
|
|
1509
1558
|
return callback(err);
|
|
@@ -1996,10 +2045,10 @@ module.exports = function(self, options) {
|
|
|
1996
2045
|
self.validateTypeChoices = function() {
|
|
1997
2046
|
_.each(self.typeChoices, function(choice) {
|
|
1998
2047
|
if (!choice.name) {
|
|
1999
|
-
throw new Error(
|
|
2048
|
+
throw new Error('One of the page types specified for your \'types\' option has no \'name\' property.');
|
|
2000
2049
|
}
|
|
2001
2050
|
if (!choice.label) {
|
|
2002
|
-
throw new Error(
|
|
2051
|
+
throw new Error('One of the page types specified for your \'types\' option has no \'label\' property.');
|
|
2003
2052
|
}
|
|
2004
2053
|
});
|
|
2005
2054
|
};
|
|
@@ -2060,7 +2109,10 @@ module.exports = function(self, options) {
|
|
|
2060
2109
|
|
|
2061
2110
|
self.removeSlugFromHomepageSchema = function(page, schema) {
|
|
2062
2111
|
if (page.level === 0) {
|
|
2063
|
-
schema = _.reject(schema, {
|
|
2112
|
+
schema = _.reject(schema, {
|
|
2113
|
+
type: 'slug',
|
|
2114
|
+
name: 'slug'
|
|
2115
|
+
});
|
|
2064
2116
|
}
|
|
2065
2117
|
return schema;
|
|
2066
2118
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
let apos;
|
|
4
|
+
let savedBaseUrl;
|
|
5
|
+
|
|
6
|
+
describe('APOS_BASE_URL environment variable', function() {
|
|
7
|
+
|
|
8
|
+
this.timeout(t.timeout);
|
|
9
|
+
|
|
10
|
+
before(function(done) {
|
|
11
|
+
savedBaseUrl = process.env.APOS_BASE_URL;
|
|
12
|
+
process.env.APOS_BASE_URL = 'https://madethisup.com';
|
|
13
|
+
apos = require('../index.js')({
|
|
14
|
+
root: module,
|
|
15
|
+
shortName: 'test',
|
|
16
|
+
afterInit: function(callback) {
|
|
17
|
+
// In tests this will be the name of the test file,
|
|
18
|
+
// so override that in order to get apostrophe to
|
|
19
|
+
// listen normally and not try to run a task. -Tom
|
|
20
|
+
apos.argv._ = [];
|
|
21
|
+
return callback(null);
|
|
22
|
+
},
|
|
23
|
+
afterListen: function(err) {
|
|
24
|
+
assert(!err);
|
|
25
|
+
done();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
after(function(done) {
|
|
31
|
+
if (savedBaseUrl) {
|
|
32
|
+
process.env.APOS_BASE_URL = savedBaseUrl;
|
|
33
|
+
} else {
|
|
34
|
+
delete process.env.APOS_BASE_URL;
|
|
35
|
+
}
|
|
36
|
+
return t.destroy(apos, done);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should respect APOS_BASE_URL', async function() {
|
|
40
|
+
const req = apos.tasks.getReq();
|
|
41
|
+
const home = await apos.docs.find(req, { slug: '/' }).toObject();
|
|
42
|
+
assert(home);
|
|
43
|
+
assert.strictEqual(home._url, 'https://madethisup.com/');
|
|
44
|
+
});
|
|
45
|
+
});
|
package/test/pages.js
CHANGED
|
@@ -433,7 +433,6 @@ describe('Pages', function() {
|
|
|
433
433
|
assert(body.match(/Home: \//));
|
|
434
434
|
// Does the response prove that data.home._children was available?
|
|
435
435
|
assert(body.match(/Tab: \/another-parent/));
|
|
436
|
-
// console.log(body);
|
|
437
436
|
return done();
|
|
438
437
|
});
|
|
439
438
|
});
|
|
@@ -447,7 +446,6 @@ describe('Pages', function() {
|
|
|
447
446
|
assert(body.match(/Home: \//));
|
|
448
447
|
// Does the response prove that data.home._children was available?
|
|
449
448
|
assert(body.match(/Tab: \/another-parent/));
|
|
450
|
-
// console.log(body);
|
|
451
449
|
return done();
|
|
452
450
|
});
|
|
453
451
|
});
|
|
@@ -475,6 +473,29 @@ describe('Pages', function() {
|
|
|
475
473
|
});
|
|
476
474
|
});
|
|
477
475
|
|
|
476
|
+
it('should redirect to url with path in lower case by default', function(done) {
|
|
477
|
+
return request('http://localhost:7900/chiLD', function (err, response, body) {
|
|
478
|
+
assert(!err);
|
|
479
|
+
assert.equal(response.statusCode, 200);
|
|
480
|
+
assert(body.match(/Sing to me, Oh Muse./));
|
|
481
|
+
assert(body.match(/Home: \//));
|
|
482
|
+
assert(body.match(/Tab: \/another-parent/));
|
|
483
|
+
return done();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should not redirect to url with path in lower case if the option is disabled', function(done) {
|
|
488
|
+
apos.pages.options.redirectFailedUpperCaseUrls = false;
|
|
489
|
+
|
|
490
|
+
return request('http://localhost:7900/chiLD', function (err, response, body) {
|
|
491
|
+
assert(!err);
|
|
492
|
+
assert.equal(response.statusCode, 404);
|
|
493
|
+
apos.pages.options.redirectFailedUpperCaseUrls = true;
|
|
494
|
+
return done();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
});
|
|
498
|
+
|
|
478
499
|
it('should detect that the home page is an ancestor of any page except itself', function() {
|
|
479
500
|
assert(
|
|
480
501
|
apos.pages.isAncestorOf({
|
|
@@ -561,8 +582,8 @@ describe('Pages', function() {
|
|
|
561
582
|
assert.equal(page.path, '/newish-page');
|
|
562
583
|
done();
|
|
563
584
|
});
|
|
564
|
-
});
|
|
565
585
|
|
|
586
|
+
});
|
|
566
587
|
});
|
|
567
588
|
|
|
568
589
|
describe('Pages with trashInSchema', function() {
|