ghost 4.22.2 → 4.24.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/.c8rc.json +24 -0
- package/Gruntfile.js +0 -1
- package/content/public/README.md +3 -0
- package/core/app.js +12 -1
- package/core/boot.js +45 -26
- package/core/bridge.js +10 -10
- package/core/built/assets/{chunk.3.324fd0cc598c73650219.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
- package/core/built/assets/{ghost-dark-39fb496d051565531062d7e047d1c0b1.css → ghost-dark-e7b57ab951512c5719aee89b16b9a448.css} +1 -1
- package/core/built/assets/{ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css → ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css} +1 -1
- package/core/built/assets/{ghost.min-7da921f6c6cac3fe10da1ba104575440.js → ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js} +138 -105
- package/core/built/assets/icons/powered-by-tenor.svg +35 -0
- package/core/built/assets/icons/tenor.svg +7 -0
- package/core/built/assets/{vendor.min-413f887176a041e6dbf88214ca9a7481.js → vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js} +2964 -2434
- package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/services/card-assets/index.js +0 -12
- package/core/frontend/services/card-assets/service.js +35 -26
- package/core/frontend/services/routing/CollectionRouter.js +4 -5
- package/core/frontend/services/routing/EmailRouter.js +1 -1
- package/core/frontend/services/routing/ParentRouter.js +0 -8
- package/core/frontend/services/routing/PreviewRouter.js +1 -1
- package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
- package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
- package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
- package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
- package/core/frontend/services/routing/router-manager.js +7 -2
- package/core/frontend/services/rss/generate-feed.js +2 -1
- package/core/frontend/src/cards/css/bookmark.css +72 -47
- package/core/frontend/src/cards/css/callout.css +41 -4
- package/core/frontend/src/cards/css/gallery.css +15 -10
- package/core/frontend/src/cards/css/nft.css +20 -11
- package/core/frontend/src/cards/css/toggle.css +58 -0
- package/core/frontend/src/cards/js/toggle.js +16 -0
- package/core/frontend/web/middleware/serve-public-file.js +39 -16
- package/core/frontend/web/site.js +11 -14
- package/core/server/api/canary/authentication.js +1 -1
- package/core/server/api/canary/utils/serializers/output/config.js +1 -1
- package/core/server/api/v2/authentication.js +1 -1
- package/core/server/api/v3/authentication.js +1 -1
- package/core/server/data/db/connection.js +7 -0
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
- package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
- package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
- package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
- package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
- package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
- package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
- package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
- package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
- package/core/server/data/schema/fixtures/index.js +8 -2
- package/core/server/services/email-analytics/jobs/index.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +5 -1
- package/core/server/services/mega/segment-parser.js +1 -2
- package/core/server/services/mega/template.js +52 -37
- package/core/server/services/nft-oembed.js +7 -21
- package/core/server/services/oembed.js +24 -24
- package/core/server/services/public-config/config.js +1 -1
- package/core/server/services/redirects/api.js +18 -23
- package/core/server/services/redirects/index.js +18 -10
- package/core/server/services/redirects/utils.js +14 -0
- package/core/server/services/redirects/validation.js +10 -0
- package/core/server/services/route-settings/index.js +40 -17
- package/core/server/services/route-settings/route-settings.js +127 -114
- package/core/server/services/route-settings/settings-loader.js +14 -32
- package/core/server/services/themes/activation-bridge.js +3 -3
- package/core/server/services/url/LocalFileCache.js +75 -0
- package/core/server/services/url/Resources.js +8 -2
- package/core/server/services/url/UrlGenerator.js +23 -20
- package/core/server/services/url/UrlService.js +75 -63
- package/core/server/services/url/index.js +17 -3
- package/core/server/web/admin/app.js +7 -10
- package/core/server/web/admin/controller.js +35 -12
- package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +1 -1
- package/core/server/web/api/canary/admin/app.js +3 -6
- package/core/server/web/api/canary/admin/middleware.js +6 -6
- package/core/server/web/api/canary/admin/routes.js +5 -5
- package/core/server/web/api/canary/content/app.js +3 -6
- package/core/server/web/api/canary/content/middleware.js +3 -3
- package/core/server/web/api/v2/admin/app.js +3 -6
- package/core/server/web/api/v2/admin/middleware.js +6 -6
- package/core/server/web/api/v2/admin/routes.js +5 -5
- package/core/server/web/api/v2/content/app.js +3 -6
- package/core/server/web/api/v2/content/middleware.js +3 -3
- package/core/server/web/api/v3/admin/app.js +3 -6
- package/core/server/web/api/v3/admin/middleware.js +6 -6
- package/core/server/web/api/v3/admin/routes.js +5 -5
- package/core/server/web/api/v3/content/app.js +3 -6
- package/core/server/web/api/v3/content/middleware.js +3 -3
- package/core/server/web/members/app.js +6 -9
- package/core/server/web/oauth/app.js +0 -4
- package/core/server/web/parent/app.js +17 -9
- package/core/server/web/parent/frontend.js +1 -1
- package/core/server/web/shared/index.js +2 -2
- package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/error-handler.js +70 -53
- package/core/server/web/shared/{middlewares → middleware}/index.js +0 -4
- package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/config/helpers.js +42 -0
- package/core/shared/config/loader.js +1 -1
- package/core/shared/labs.js +7 -2
- package/loggingrc.js +19 -20
- package/package.json +35 -34
- package/yarn.lock +822 -345
- package/core/server/data/schema/fixtures/utils.js +0 -321
- package/core/server/web/parent/vhost-utils.js +0 -39
- package/core/server/web/shared/middlewares/maintenance.js +0 -25
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const logging = require('@tryghost/logging');
|
|
2
2
|
const merge = require('lodash/merge');
|
|
3
3
|
const models = require('../../../../models');
|
|
4
|
-
const
|
|
4
|
+
const {fixtureManager} = require('../../../schema/fixtures');
|
|
5
5
|
|
|
6
6
|
const _private = {};
|
|
7
7
|
|
|
@@ -15,12 +15,12 @@ _private.printResult = function printResult(result, message) {
|
|
|
15
15
|
|
|
16
16
|
_private.addGhostSchedulerIntegration = (options) => {
|
|
17
17
|
const message = 'Adding "Ghost Scheduler" integration';
|
|
18
|
-
const fixtureIntegration =
|
|
18
|
+
const fixtureIntegration = fixtureManager.findModelFixtureEntry('Integration', {slug: 'ghost-scheduler'});
|
|
19
19
|
|
|
20
20
|
return models.Integration.findOne({slug: fixtureIntegration.slug}, options)
|
|
21
21
|
.then((integration) => {
|
|
22
22
|
if (!integration) {
|
|
23
|
-
return
|
|
23
|
+
return fixtureManager.addFixturesForModel({
|
|
24
24
|
name: 'Integration',
|
|
25
25
|
entries: [fixtureIntegration]
|
|
26
26
|
}, options).then(result => _private.printResult(result, message));
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {(val: string) => boolean} exists
|
|
6
|
+
* @param {string} requested
|
|
7
|
+
* @param {string} attempt
|
|
8
|
+
* @param {number} n
|
|
9
|
+
*
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
function getUnique(exists, requested, attempt = requested, n = 1) {
|
|
13
|
+
if (!exists(attempt)) {
|
|
14
|
+
return attempt;
|
|
15
|
+
}
|
|
16
|
+
const newAttempt = requested.slice(0, -n.toString().length) + n;
|
|
17
|
+
return getUnique(exists, requested, newAttempt, n + 1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = createTransactionalMigration(
|
|
21
|
+
async function up(knex) {
|
|
22
|
+
const allOffers = await knex
|
|
23
|
+
.select('id', 'name')
|
|
24
|
+
.from('offers');
|
|
25
|
+
|
|
26
|
+
const offersNeedingTrunctation = allOffers.filter((row) => {
|
|
27
|
+
return row.name.length >= 40;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (offersNeedingTrunctation.length === 0) {
|
|
31
|
+
logging.warn('No Offers found needing truncation');
|
|
32
|
+
return;
|
|
33
|
+
} else {
|
|
34
|
+
logging.info(`Found ${offersNeedingTrunctation.length} Offers needing truncation`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const truncatedOffers = offersNeedingTrunctation.reduce((offers, row) => {
|
|
38
|
+
function exists(name) {
|
|
39
|
+
return offers.find(offer => offer.name === name) !== undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const updatedRow = {
|
|
43
|
+
id: row.id,
|
|
44
|
+
name: getUnique(exists, row.name.slice(0, 40))
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return offers.concat(updatedRow);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
for (const truncatedOffer of truncatedOffers) {
|
|
51
|
+
await knex('offers')
|
|
52
|
+
.update('name', truncatedOffer.name)
|
|
53
|
+
.where('id', truncatedOffer.id);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
// no-op we've lost the data required to roll this back
|
|
57
|
+
async function down() {}
|
|
58
|
+
);
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const Promise = require('bluebird');
|
|
3
|
+
const logging = require('@tryghost/logging');
|
|
4
|
+
const {sequence} = require('@tryghost/promise');
|
|
5
|
+
|
|
6
|
+
const models = require('../../../models');
|
|
7
|
+
const baseUtils = require('../../../models/base/utils');
|
|
8
|
+
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
|
|
11
|
+
class FixtureManager {
|
|
12
|
+
constructor(fixtures) {
|
|
13
|
+
this.fixtures = fixtures;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ### Match Func
|
|
18
|
+
* Figures out how to match across various combinations of keys and values.
|
|
19
|
+
* Match can be a string or an array containing 2 strings
|
|
20
|
+
* Key and Value are the values to be found
|
|
21
|
+
* Value can also be an array, in which case we look for a match in the array.
|
|
22
|
+
* @api private
|
|
23
|
+
* @param {String|Array} match
|
|
24
|
+
* @param {String|Integer} key
|
|
25
|
+
* @param {String|Array} [value]
|
|
26
|
+
* @returns {Function} matching function
|
|
27
|
+
*/
|
|
28
|
+
static matchFunc(match, key, value) {
|
|
29
|
+
if (_.isArray(match)) {
|
|
30
|
+
return function (item) {
|
|
31
|
+
let valueTest = true;
|
|
32
|
+
|
|
33
|
+
if (_.isArray(value)) {
|
|
34
|
+
valueTest = value.indexOf(item.get(match[1])) > -1;
|
|
35
|
+
} else if (value !== 'all') {
|
|
36
|
+
valueTest = item.get(match[1]) === value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return item.get(match[0]) === key && valueTest;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return function (item) {
|
|
44
|
+
key = key === 0 && value ? value : key;
|
|
45
|
+
return item.get(match) === key;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static matchObj(match, item) {
|
|
50
|
+
const matchedObj = {};
|
|
51
|
+
|
|
52
|
+
if (_.isArray(match)) {
|
|
53
|
+
_.each(match, (matchProp) => {
|
|
54
|
+
matchedObj[matchProp] = item.get(matchProp);
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
matchedObj[match] = item.get(match);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return matchedObj;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Add All Fixtures
|
|
65
|
+
*
|
|
66
|
+
* Helper method to handle adding all fixtures
|
|
67
|
+
*
|
|
68
|
+
* @param {object} options
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
async addAllFixtures(options) {
|
|
72
|
+
const localOptions = _.merge({
|
|
73
|
+
context: {internal: true},
|
|
74
|
+
migrating: true
|
|
75
|
+
}, options);
|
|
76
|
+
|
|
77
|
+
await Promise.mapSeries(this.fixtures.models, (model) => {
|
|
78
|
+
logging.info('Model: ' + model.name);
|
|
79
|
+
|
|
80
|
+
return this.addFixturesForModel(model, localOptions);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Promise.mapSeries(this.fixtures.relations, (relation) => {
|
|
84
|
+
logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
|
|
85
|
+
return this.addFixturesForRelation(relation, localOptions);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Find methods - use the local fixtures
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* ### Find Model Fixture
|
|
95
|
+
* Finds a model fixture based on model name
|
|
96
|
+
* @api private
|
|
97
|
+
* @param {String} modelName
|
|
98
|
+
* @returns {Object} model fixture
|
|
99
|
+
*/
|
|
100
|
+
findModelFixture(modelName) {
|
|
101
|
+
return _.find(this.fixtures.models, (modelFixture) => {
|
|
102
|
+
return modelFixture.name === modelName;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* ### Find Model Fixture Entry
|
|
108
|
+
* Find a single model fixture entry by model name & a matching expression for the FIND function
|
|
109
|
+
* @param {String} modelName
|
|
110
|
+
* @param {String|Object|Function} matchExpr
|
|
111
|
+
* @returns {Object} model fixture entry
|
|
112
|
+
*/
|
|
113
|
+
findModelFixtureEntry(modelName, matchExpr) {
|
|
114
|
+
return _.find(this.findModelFixture(modelName).entries, matchExpr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ### Find Model Fixtures
|
|
119
|
+
* Find a model fixture name & a matching expression for the FILTER function
|
|
120
|
+
* @param {String} modelName
|
|
121
|
+
* @param {String|Object|Function} matchExpr
|
|
122
|
+
* @returns {Object} model fixture
|
|
123
|
+
*/
|
|
124
|
+
findModelFixtures(modelName, matchExpr) {
|
|
125
|
+
const foundModel = _.cloneDeep(this.findModelFixture(modelName));
|
|
126
|
+
foundModel.entries = _.filter(foundModel.entries, matchExpr);
|
|
127
|
+
return foundModel;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* ### Find Relation Fixture
|
|
132
|
+
* Find a relation fixture by from & to models
|
|
133
|
+
* @api private
|
|
134
|
+
* @param {String} from
|
|
135
|
+
* @param {String} to
|
|
136
|
+
* @returns {Object} relation fixture
|
|
137
|
+
*/
|
|
138
|
+
findRelationFixture(from, to) {
|
|
139
|
+
return _.find(this.fixtures.relations, (relation) => {
|
|
140
|
+
return relation.from.model === from && relation.to.model === to;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* ### Find Permission Relations For Object
|
|
146
|
+
* Specialist function can return the permission relation fixture with only entries for a particular object.model
|
|
147
|
+
* @param {String} objName
|
|
148
|
+
* @returns {Object} fixture relation
|
|
149
|
+
*/
|
|
150
|
+
findPermissionRelationsForObject(objName, role) {
|
|
151
|
+
// Make a copy and delete any entries we don't want
|
|
152
|
+
const foundRelation = _.cloneDeep(this.findRelationFixture('Role', 'Permission'));
|
|
153
|
+
|
|
154
|
+
_.each(foundRelation.entries, (entry, key) => {
|
|
155
|
+
_.each(entry, (perm, obj) => {
|
|
156
|
+
if (obj !== objName) {
|
|
157
|
+
delete entry[obj];
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (_.isEmpty(entry) || (role && role !== key)) {
|
|
162
|
+
delete foundRelation.entries[key];
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return foundRelation;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/******************************************************
|
|
170
|
+
* From here down, the methods require access to models
|
|
171
|
+
* But aren't dependent on this.fixtures
|
|
172
|
+
******************************************************/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* ### Fetch Relation Data
|
|
176
|
+
* Before we build relations we need to fetch all of the models from both sides so that we can
|
|
177
|
+
* use filter and find to quickly locate the correct models.
|
|
178
|
+
* @api private
|
|
179
|
+
* @param {{from, to, entries}} relation
|
|
180
|
+
* @returns {Promise<*>}
|
|
181
|
+
*/
|
|
182
|
+
fetchRelationData(relation, options) {
|
|
183
|
+
const fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]});
|
|
184
|
+
|
|
185
|
+
const props = {
|
|
186
|
+
from: models[relation.from.model].findAll(fromOptions),
|
|
187
|
+
to: models[relation.to.model].findAll(options)
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return Promise.props(props);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* ### Add Fixtures for Model
|
|
195
|
+
* Takes a model fixture, with a name and some entries and processes these
|
|
196
|
+
* into a sequence of promises to get each fixture added.
|
|
197
|
+
*
|
|
198
|
+
* @param {{name, entries}} modelFixture
|
|
199
|
+
* @returns {Promise<any>}
|
|
200
|
+
*/
|
|
201
|
+
async addFixturesForModel(modelFixture, options = {}) {
|
|
202
|
+
// Clone the fixtures as they get changed in this function.
|
|
203
|
+
// The initial blog posts will be added a `published_at` property, which
|
|
204
|
+
// would change the fixturesHash.
|
|
205
|
+
modelFixture = _.cloneDeep(modelFixture);
|
|
206
|
+
// The Post model fixtures need a `published_at` date, where at least the seconds
|
|
207
|
+
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
|
208
|
+
// them.
|
|
209
|
+
if (modelFixture.name === 'Post') {
|
|
210
|
+
_.forEach(modelFixture.entries, (post, index) => {
|
|
211
|
+
if (!post.published_at) {
|
|
212
|
+
post.published_at = moment().add(index, 'seconds');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
218
|
+
let data = {};
|
|
219
|
+
|
|
220
|
+
// CASE: if id is specified, only query by id
|
|
221
|
+
if (entry.id) {
|
|
222
|
+
data.id = entry.id;
|
|
223
|
+
} else if (entry.slug) {
|
|
224
|
+
data.slug = entry.slug;
|
|
225
|
+
} else {
|
|
226
|
+
data = _.cloneDeep(entry);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (modelFixture.name === 'Post') {
|
|
230
|
+
data.status = 'all';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const found = await models[modelFixture.name].findOne(data, options);
|
|
234
|
+
if (!found) {
|
|
235
|
+
return models[modelFixture.name].add(entry, options);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return {expected: modelFixture.entries.length, done: _.compact(results).length};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* ## Add Fixtures for Relation
|
|
244
|
+
* Takes a relation fixtures object, with a from, to and some entries and processes these
|
|
245
|
+
* into a sequence of promises, to get each fixture added.
|
|
246
|
+
*
|
|
247
|
+
* @param {{from, to, entries}} relationFixture
|
|
248
|
+
* @returns {Promise<any>}
|
|
249
|
+
*/
|
|
250
|
+
async addFixturesForRelation(relationFixture, options) {
|
|
251
|
+
const ops = [];
|
|
252
|
+
let max = 0;
|
|
253
|
+
|
|
254
|
+
const data = await this.fetchRelationData(relationFixture, options);
|
|
255
|
+
|
|
256
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
257
|
+
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
|
258
|
+
|
|
259
|
+
// CASE: You add new fixtures e.g. a new role in a new release.
|
|
260
|
+
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
|
261
|
+
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
|
262
|
+
if (!fromItem) {
|
|
263
|
+
logging.warn('Skip: Target database entry not found for key: ' + key);
|
|
264
|
+
return Promise.resolve();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_.each(entry, (value, entryKey) => {
|
|
268
|
+
let toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
|
269
|
+
max += toItems.length;
|
|
270
|
+
|
|
271
|
+
// Remove any duplicates that already exist in the collection
|
|
272
|
+
toItems = _.reject(toItems, (item) => {
|
|
273
|
+
return fromItem
|
|
274
|
+
.related(relationFixture.from.relation)
|
|
275
|
+
.find((model) => {
|
|
276
|
+
const objectToMatch = FixtureManager.matchObj(relationFixture.to.match, item);
|
|
277
|
+
return Object.keys(objectToMatch).every((keyToCheck) => {
|
|
278
|
+
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (toItems && toItems.length > 0) {
|
|
284
|
+
ops.push(function addRelationItems() {
|
|
285
|
+
return baseUtils.attach(
|
|
286
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
287
|
+
fromItem.id,
|
|
288
|
+
relationFixture.from.relation,
|
|
289
|
+
toItems,
|
|
290
|
+
options
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const result = await sequence(ops);
|
|
298
|
+
return {expected: max, done: _(result).map('length').sum()};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async removeFixturesForModel(modelFixture, options) {
|
|
302
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
303
|
+
const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
|
|
304
|
+
if (found) {
|
|
305
|
+
return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return {expected: modelFixture.entries.length, done: results.length};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async removeFixturesForRelation(relationFixture, options) {
|
|
313
|
+
const data = await this.fetchRelationData(relationFixture, options);
|
|
314
|
+
const ops = [];
|
|
315
|
+
|
|
316
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
317
|
+
const fromItem = data.from.find(FixtureManager.matchFunc(relationFixture.from.match, key));
|
|
318
|
+
|
|
319
|
+
_.each(entry, (value, entryKey) => {
|
|
320
|
+
const toItems = data.to.filter(FixtureManager.matchFunc(relationFixture.to.match, entryKey, value));
|
|
321
|
+
|
|
322
|
+
if (toItems && toItems.length > 0) {
|
|
323
|
+
ops.push(function detachRelation() {
|
|
324
|
+
return baseUtils.detach(
|
|
325
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
326
|
+
fromItem.id,
|
|
327
|
+
relationFixture.from.relation,
|
|
328
|
+
toItems,
|
|
329
|
+
options
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return await sequence(ops);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = FixtureManager;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const FixtureManager = require('./fixture-manager');
|
|
2
|
+
const config = require('../../../../shared/config');
|
|
3
|
+
|
|
4
|
+
const fixturePath = config.get('paths').fixtures;
|
|
5
|
+
const fixtures = require(fixturePath);
|
|
6
|
+
|
|
7
|
+
module.exports.FixtureManager = FixtureManager;
|
|
8
|
+
module.exports.fixtureManager = new FixtureManager(fixtures);
|
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
!hasScheduled &&
|
|
13
13
|
config.get('emailAnalytics') &&
|
|
14
14
|
config.get('backgroundJobs:emailAnalytics') &&
|
|
15
|
-
!process.env.NODE_ENV.
|
|
15
|
+
!process.env.NODE_ENV.startsWith('test')
|
|
16
16
|
) {
|
|
17
17
|
// Don't register email analytics job if we have no emails,
|
|
18
18
|
// processor usage from many sites spinning up threads can be high.
|
|
@@ -3,7 +3,6 @@ const template = require('./template');
|
|
|
3
3
|
const settingsCache = require('../../../shared/settings-cache');
|
|
4
4
|
const urlUtils = require('../../../shared/url-utils');
|
|
5
5
|
const moment = require('moment-timezone');
|
|
6
|
-
const cheerio = require('cheerio');
|
|
7
6
|
const api = require('../../api');
|
|
8
7
|
const {URL} = require('url');
|
|
9
8
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
@@ -24,6 +23,8 @@ const formatHtmlForEmail = function formatHtmlForEmail(html) {
|
|
|
24
23
|
|
|
25
24
|
// convert juiced HTML to a DOM-like interface for further manipulation
|
|
26
25
|
// happens after inlining of CSS so we can change element types without worrying about styling
|
|
26
|
+
|
|
27
|
+
const cheerio = require('cheerio');
|
|
27
28
|
const _cheerio = cheerio.load(juicedHtml);
|
|
28
29
|
|
|
29
30
|
// force all links to open in new tab
|
|
@@ -243,6 +244,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
|
|
|
243
244
|
|
|
244
245
|
// perform any email specific adjustments to the mobiledoc->HTML render output
|
|
245
246
|
// body wrapper is required so we can get proper top-level selections
|
|
247
|
+
const cheerio = require('cheerio');
|
|
246
248
|
let _cheerio = cheerio.load(`<body>${post.html}</body>`);
|
|
247
249
|
// remove leading/trailing HRs
|
|
248
250
|
_cheerio(`
|
|
@@ -311,6 +313,8 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi
|
|
|
311
313
|
};
|
|
312
314
|
|
|
313
315
|
function renderEmailForSegment(email, memberSegment) {
|
|
316
|
+
const cheerio = require('cheerio');
|
|
317
|
+
|
|
314
318
|
const result = {...email};
|
|
315
319
|
const $ = cheerio.load(result.html);
|
|
316
320
|
|
|
@@ -544,60 +544,75 @@ figure blockquote p {
|
|
|
544
544
|
border-width: 0.8em 0 0.8em 1.5em;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
|
|
547
|
+
.kg-nft-link {
|
|
548
|
+
display: block;
|
|
549
|
+
text-decoration: none !important;
|
|
550
|
+
color: #15212A !important;
|
|
551
|
+
font-family: inherit !important;
|
|
552
|
+
font-size: 14px;
|
|
553
|
+
line-height: 1.3em;
|
|
554
|
+
padding-top: 4px;
|
|
555
|
+
padding-right: 20px;
|
|
556
|
+
padding-left: 20px;
|
|
557
|
+
padding-bottom: 4px;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.kg-card-callout {
|
|
548
561
|
display: flex;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
|
552
|
-
font-size: 15px;
|
|
553
|
-
text-decoration: none;
|
|
562
|
+
margin: 0 0 1.5em 0;
|
|
563
|
+
padding: 20px 28px;
|
|
554
564
|
border-radius: 3px;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.kg-card-callout p {
|
|
568
|
+
margin: 0
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.kg-card-callout-grey {
|
|
572
|
+
background: #eef0f2;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.kg-card-callout-white {
|
|
559
576
|
background: #fff;
|
|
560
|
-
|
|
577
|
+
box-shadow: inset 0 0 0 1px #dddedf;
|
|
561
578
|
}
|
|
562
579
|
|
|
563
|
-
.kg-
|
|
564
|
-
|
|
580
|
+
.kg-card-callout-blue {
|
|
581
|
+
background: #E9F6FB;
|
|
565
582
|
}
|
|
566
583
|
|
|
567
|
-
.kg-
|
|
568
|
-
|
|
584
|
+
.kg-card-callout-green {
|
|
585
|
+
background: #E8F8EA;
|
|
569
586
|
}
|
|
570
587
|
|
|
571
|
-
.kg-
|
|
572
|
-
|
|
573
|
-
justify-content: space-between;
|
|
574
|
-
gap: 20px;
|
|
588
|
+
.kg-card-callout-yellow {
|
|
589
|
+
background: #FCF4E3;
|
|
575
590
|
}
|
|
576
591
|
|
|
577
|
-
.kg-
|
|
578
|
-
|
|
579
|
-
font-size: 19px;
|
|
580
|
-
font-weight: 700;
|
|
581
|
-
margin: 0;
|
|
582
|
-
color: #222;
|
|
592
|
+
.kg-card-callout-red {
|
|
593
|
+
background: #FBE9E9;
|
|
583
594
|
}
|
|
584
595
|
|
|
585
|
-
.kg-
|
|
586
|
-
|
|
587
|
-
margin: 4px 0 0;
|
|
588
|
-
color: #ababab;
|
|
596
|
+
.kg-card-callout-pink {
|
|
597
|
+
background: #FCEEF8;
|
|
589
598
|
}
|
|
590
599
|
|
|
591
|
-
.kg-
|
|
592
|
-
|
|
593
|
-
color: #222;
|
|
600
|
+
.kg-card-callout-purple {
|
|
601
|
+
background: #F2EDFC;
|
|
594
602
|
}
|
|
595
603
|
|
|
596
|
-
.kg-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
604
|
+
.kg-card-callout-accent {
|
|
605
|
+
background: ${templateSettings.accentColor || '#15212A'};
|
|
606
|
+
color: #fff;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.kg-card-callout-accent a {
|
|
610
|
+
color: #fff;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.kg-callout-emoji {
|
|
614
|
+
padding-right: 12px;
|
|
615
|
+
font-size: 20px;
|
|
601
616
|
}
|
|
602
617
|
|
|
603
618
|
/* -------------------------------------
|
|
@@ -28,42 +28,28 @@ class NFTOEmbedProvider {
|
|
|
28
28
|
* @param {URL} url
|
|
29
29
|
* @param {IExternalRequest} externalRequest
|
|
30
30
|
*
|
|
31
|
-
* @returns {Promise<
|
|
31
|
+
* @returns {Promise<object>}
|
|
32
32
|
*/
|
|
33
33
|
async getOEmbedData(url, externalRequest) {
|
|
34
34
|
const [match, transaction, asset] = url.pathname.match(OPENSEA_PATH_REGEX);
|
|
35
35
|
if (!match) {
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
|
-
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}
|
|
38
|
+
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
|
|
39
39
|
json: true
|
|
40
40
|
});
|
|
41
41
|
return {
|
|
42
42
|
version: '1.0',
|
|
43
|
-
type: '
|
|
43
|
+
type: 'nft',
|
|
44
44
|
title: result.body.name,
|
|
45
45
|
author_name: result.body.creator.user.username,
|
|
46
46
|
author_url: `https://opensea.io/${result.body.creator.user.username}`,
|
|
47
47
|
provider_name: 'OpenSea',
|
|
48
48
|
provider_url: 'https://opensea.io',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<div class="kg-nft-header">
|
|
54
|
-
<h4 class="kg-nft-title"> ${result.body.name} </h4>
|
|
55
|
-
</div>
|
|
56
|
-
<div class="kg-nft-creator">
|
|
57
|
-
Created by <span class="kg-nft-creator-name">${result.body.creator.user.username}</span>
|
|
58
|
-
${(result.body.collection.name ? `• ${result.body.collection.name}` : ``)}
|
|
59
|
-
</div>
|
|
60
|
-
${(result.body.description ? `<p class="kg-nft-description">${result.body.description}</p>` : ``)}
|
|
61
|
-
</div>
|
|
62
|
-
</a>
|
|
63
|
-
`,
|
|
64
|
-
width: 1000,
|
|
65
|
-
height: 1000,
|
|
66
|
-
noIframe: true
|
|
49
|
+
image_url: result.body.image_url,
|
|
50
|
+
creator_name: result.body.creator.user.username,
|
|
51
|
+
description: result.body.description,
|
|
52
|
+
collection_name: result.body.collection.name
|
|
67
53
|
};
|
|
68
54
|
}
|
|
69
55
|
}
|