ghost 4.19.1 → 4.20.3
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/.eslintrc.js +9 -8
- package/Gruntfile.js +1 -1
- package/PRIVACY.md +3 -0
- package/content/adapters/README.md +2 -2
- package/core/boot.js +4 -4
- package/core/bridge.js +9 -1
- package/core/built/assets/{chunk.3.0778d8e4d707d2a625f1.js → chunk.3.777d43e2ce954ba8b2f5.js} +1 -1
- package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
- package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
- package/core/built/assets/{ghost.min-102753ec485602c8fe80d60a1750bf84.js → ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js} +525 -340
- package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
- package/core/built/assets/icons/arrow-left-small.svg +0 -4
- package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
- package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
- package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
- package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
- package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
- package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
- package/core/built/assets/{vendor.min-0916203b598271a795909e8e0b1c16c2.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +1046 -1043
- package/core/frontend/apps/amp/lib/router.js +1 -1
- package/core/frontend/meta/author-url.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/{server → frontend}/public/favicon.ico +0 -0
- package/core/{server → frontend}/public/ghost.css +0 -0
- package/core/{server → frontend}/public/ghost.min.css +0 -0
- package/core/{server → frontend}/public/robots.txt +0 -0
- package/core/{server → frontend}/public/sitemap.xsl +0 -0
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/CollectionRouter.js +3 -49
- package/core/frontend/services/routing/ParentRouter.js +1 -4
- package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
- package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
- package/core/frontend/services/routing/controllers/collection.js +2 -2
- package/core/frontend/services/routing/controllers/email-post.js +2 -2
- package/core/frontend/services/routing/controllers/entry.js +2 -2
- package/core/frontend/services/routing/controllers/preview.js +2 -2
- package/core/frontend/services/routing/index.js +6 -12
- package/core/frontend/services/routing/registry.js +13 -0
- package/core/frontend/services/routing/router-manager.js +185 -0
- package/core/frontend/services/rss/generate-feed.js +2 -2
- package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
- package/core/frontend/services/theme-engine/i18n/index.js +1 -1
- package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
- package/core/frontend/web/index.js +1 -0
- package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
- package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
- package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
- package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
- package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
- package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
- package/core/{server/web/site → frontend/web}/routes.js +5 -4
- package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
- package/core/server/adapters/storage/LocalFileStorage.js +35 -39
- package/core/server/adapters/storage/index.js +12 -2
- package/core/server/api/canary/images.js +1 -1
- package/core/server/api/canary/offers.js +19 -0
- package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
- package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v2/images.js +1 -1
- package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v3/images.js +1 -1
- package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
- package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
- package/core/server/data/importer/handlers/image.js +1 -1
- package/core/server/data/importer/importers/image.js +1 -1
- package/core/server/data/migrations/init/1-create-tables.js +7 -8
- package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
- package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
- package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
- package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
- package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
- package/core/server/data/schema/fixtures/utils.js +150 -143
- package/core/server/data/schema/schema.js +4 -3
- package/core/server/frontend/ghost.min.css +1 -0
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/lib/mobiledoc.js +3 -2
- package/core/server/models/action.js +7 -4
- package/core/server/models/base/plugins/overrides.js +19 -6
- package/core/server/models/index.js +4 -46
- package/core/server/models/member.js +5 -0
- package/core/server/models/user.js +2 -1
- package/core/server/overrides.js +6 -2
- package/core/server/services/adapter-manager/config.js +1 -0
- package/core/server/services/adapter-manager/index.js +9 -5
- package/core/server/services/adapter-manager/options-resolver.js +18 -0
- package/core/server/services/bulk-email/mailgun.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/members/api.js +1 -3
- package/core/server/services/members/emails/signin.js +1 -1
- package/core/server/services/members/emails/signup.js +1 -1
- package/core/server/services/members/emails/subscribe.js +1 -1
- package/core/server/services/members/service.js +2 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/route-settings/route-settings.js +1 -1
- package/core/server/services/settings/index.js +3 -1
- package/core/server/services/settings/settings-bread-service.js +42 -20
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activate.js +2 -2
- package/core/server/services/themes/activation-bridge.js +6 -6
- package/core/server/services/themes/storage.js +1 -1
- package/core/{frontend → server}/services/url/Queue.js +0 -0
- package/core/{frontend → server}/services/url/Resource.js +0 -0
- package/core/{frontend → server}/services/url/Resources.js +2 -2
- package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
- package/core/{frontend → server}/services/url/UrlService.js +12 -15
- package/core/{frontend → server}/services/url/Urls.js +1 -1
- package/core/{frontend → server}/services/url/configs/canary.js +0 -0
- package/core/{frontend → server}/services/url/configs/v2.js +0 -0
- package/core/{frontend → server}/services/url/configs/v3.js +0 -0
- package/core/{frontend → server}/services/url/configs/v4.js +0 -0
- package/core/{frontend → server}/services/url/index.js +0 -0
- package/core/server/services/xmlrpc.js +1 -1
- package/core/server/update-check.js +3 -3
- package/core/server/web/admin/controller.js +11 -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 +8 -9
- package/core/server/web/oauth/app.js +4 -2
- package/core/server/web/parent/backend.js +3 -3
- package/core/server/web/parent/frontend.js +2 -2
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
- package/core/server/web/shared/middlewares/maintenance.js +1 -1
- package/core/server/web/well-known.js +10 -10
- package/core/shared/config/overrides.json +1 -1
- package/core/shared/express.js +10 -0
- package/core/shared/html-to-plaintext.js +2 -2
- package/core/shared/labs.js +14 -5
- package/package.json +45 -43
- package/yarn.lock +649 -284
- package/core/built/assets/ghost-dark-da8e8eba130fb52f97494e51850d1045.css +0 -1
- package/core/built/assets/ghost.min-0d8f19623e9f077351bce453034daf4d.css +0 -1
- package/core/frontend/services/routing/bootstrap.js +0 -134
- package/core/server/public/404-ghost.png +0 -0
- package/core/server/public/404-ghost@2x.png +0 -0
- package/core/server/web/site/index.js +0 -1
- package/core/shared/i18n/i18n.js +0 -312
- package/core/shared/i18n/index.js +0 -6
|
@@ -47,7 +47,7 @@ const matchObj = function matchObj(match, item) {
|
|
|
47
47
|
const matchedObj = {};
|
|
48
48
|
|
|
49
49
|
if (_.isArray(match)) {
|
|
50
|
-
_.each(match,
|
|
50
|
+
_.each(match, (matchProp) => {
|
|
51
51
|
matchedObj[matchProp] = item.get(matchProp);
|
|
52
52
|
});
|
|
53
53
|
} else {
|
|
@@ -76,115 +76,9 @@ const fetchRelationData = function fetchRelationData(relation, options) {
|
|
|
76
76
|
return Promise.props(props);
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
*
|
|
81
|
-
* Takes a model fixture, with a name and some entries and processes these
|
|
82
|
-
* into a sequence of promises to get each fixture added.
|
|
83
|
-
*
|
|
84
|
-
* @param {{name, entries}} modelFixture
|
|
85
|
-
* @returns {Promise.<*>}
|
|
79
|
+
/*
|
|
80
|
+
* Find methods - use the local fixtures
|
|
86
81
|
*/
|
|
87
|
-
const addFixturesForModel = function addFixturesForModel(modelFixture, options = {}) {
|
|
88
|
-
// Clone the fixtures as they get changed in this function.
|
|
89
|
-
// The initial blog posts will be added a `published_at` property, which
|
|
90
|
-
// would change the fixturesHash.
|
|
91
|
-
modelFixture = _.cloneDeep(modelFixture);
|
|
92
|
-
// The Post model fixtures need a `published_at` date, where at least the seconds
|
|
93
|
-
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
|
94
|
-
// them.
|
|
95
|
-
if (modelFixture.name === 'Post') {
|
|
96
|
-
_.forEach(modelFixture.entries, function (post, index) {
|
|
97
|
-
if (!post.published_at) {
|
|
98
|
-
post.published_at = moment().add(index, 'seconds');
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return Promise.mapSeries(modelFixture.entries, function (entry) {
|
|
104
|
-
let data = {};
|
|
105
|
-
|
|
106
|
-
// CASE: if id is specified, only query by id
|
|
107
|
-
if (entry.id) {
|
|
108
|
-
data.id = entry.id;
|
|
109
|
-
} else if (entry.slug) {
|
|
110
|
-
data.slug = entry.slug;
|
|
111
|
-
} else {
|
|
112
|
-
data = _.cloneDeep(entry);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (modelFixture.name === 'Post') {
|
|
116
|
-
data.status = 'all';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return models[modelFixture.name].findOne(data, options).then(function (found) {
|
|
120
|
-
if (!found) {
|
|
121
|
-
return models[modelFixture.name].add(entry, options);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}).then(function (results) {
|
|
125
|
-
return {expected: modelFixture.entries.length, done: _.compact(results).length};
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* ## Add Fixtures for Relation
|
|
131
|
-
* Takes a relation fixtures object, with a from, to and some entries and processes these
|
|
132
|
-
* into a sequence of promises, to get each fixture added.
|
|
133
|
-
*
|
|
134
|
-
* @param {{from, to, entries}} relationFixture
|
|
135
|
-
* @returns {Promise.<*>}
|
|
136
|
-
*/
|
|
137
|
-
const addFixturesForRelation = function addFixturesForRelation(relationFixture, options) {
|
|
138
|
-
const ops = [];
|
|
139
|
-
let max = 0;
|
|
140
|
-
|
|
141
|
-
return fetchRelationData(relationFixture, options).then(function getRelationOps(data) {
|
|
142
|
-
_.each(relationFixture.entries, function processEntries(entry, key) {
|
|
143
|
-
const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
|
|
144
|
-
|
|
145
|
-
// CASE: You add new fixtures e.g. a new role in a new release.
|
|
146
|
-
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
|
147
|
-
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
|
148
|
-
if (!fromItem) {
|
|
149
|
-
logging.warn('Skip: Target database entry not found for key: ' + key);
|
|
150
|
-
return Promise.resolve();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
_.each(entry, function processEntryValues(value, entryKey) {
|
|
154
|
-
let toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
|
|
155
|
-
max += toItems.length;
|
|
156
|
-
|
|
157
|
-
// Remove any duplicates that already exist in the collection
|
|
158
|
-
toItems = _.reject(toItems, function (item) {
|
|
159
|
-
return fromItem
|
|
160
|
-
.related(relationFixture.from.relation)
|
|
161
|
-
.find((model) => {
|
|
162
|
-
const objectToMatch = matchObj(relationFixture.to.match, item);
|
|
163
|
-
return Object.keys(objectToMatch).every(function (keyToCheck) {
|
|
164
|
-
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
if (toItems && toItems.length > 0) {
|
|
170
|
-
ops.push(function addRelationItems() {
|
|
171
|
-
return baseUtils.attach(
|
|
172
|
-
models[relationFixture.from.Model || relationFixture.from.model],
|
|
173
|
-
fromItem.id,
|
|
174
|
-
relationFixture.from.relation,
|
|
175
|
-
toItems,
|
|
176
|
-
options
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
return sequence(ops);
|
|
184
|
-
}).then(function (result) {
|
|
185
|
-
return {expected: max, done: _(result).map('length').sum()};
|
|
186
|
-
});
|
|
187
|
-
};
|
|
188
82
|
|
|
189
83
|
/**
|
|
190
84
|
* ### Find Model Fixture
|
|
@@ -194,7 +88,7 @@ const addFixturesForRelation = function addFixturesForRelation(relationFixture,
|
|
|
194
88
|
* @returns {Object} model fixture
|
|
195
89
|
*/
|
|
196
90
|
const findModelFixture = function findModelFixture(modelName) {
|
|
197
|
-
return _.find(fixtures.models,
|
|
91
|
+
return _.find(fixtures.models, (modelFixture) => {
|
|
198
92
|
return modelFixture.name === modelName;
|
|
199
93
|
});
|
|
200
94
|
};
|
|
@@ -232,7 +126,7 @@ const findModelFixtures = function findModelFixtures(modelName, matchExpr) {
|
|
|
232
126
|
* @returns {Object} relation fixture
|
|
233
127
|
*/
|
|
234
128
|
const findRelationFixture = function findRelationFixture(from, to) {
|
|
235
|
-
return _.find(fixtures.relations,
|
|
129
|
+
return _.find(fixtures.relations, (relation) => {
|
|
236
130
|
return relation.from.model === from && relation.to.model === to;
|
|
237
131
|
});
|
|
238
132
|
};
|
|
@@ -247,8 +141,8 @@ const findPermissionRelationsForObject = function findPermissionRelationsForObje
|
|
|
247
141
|
// Make a copy and delete any entries we don't want
|
|
248
142
|
const foundRelation = _.cloneDeep(findRelationFixture('Role', 'Permission'));
|
|
249
143
|
|
|
250
|
-
_.each(foundRelation.entries,
|
|
251
|
-
_.each(entry,
|
|
144
|
+
_.each(foundRelation.entries, (entry, key) => {
|
|
145
|
+
_.each(entry, (perm, obj) => {
|
|
252
146
|
if (obj !== objName) {
|
|
253
147
|
delete entry[obj];
|
|
254
148
|
}
|
|
@@ -262,53 +156,166 @@ const findPermissionRelationsForObject = function findPermissionRelationsForObje
|
|
|
262
156
|
return foundRelation;
|
|
263
157
|
};
|
|
264
158
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
159
|
+
/*
|
|
160
|
+
* Add and Remove Functions, require access to models
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* ### Add Fixtures for Model
|
|
165
|
+
* Takes a model fixture, with a name and some entries and processes these
|
|
166
|
+
* into a sequence of promises to get each fixture added.
|
|
167
|
+
*
|
|
168
|
+
* @param {{name, entries}} modelFixture
|
|
169
|
+
* @returns {Promise<any>}
|
|
170
|
+
*/
|
|
171
|
+
const addFixturesForModel = async function addFixturesForModel(modelFixture, options = {}) {
|
|
172
|
+
// Clone the fixtures as they get changed in this function.
|
|
173
|
+
// The initial blog posts will be added a `published_at` property, which
|
|
174
|
+
// would change the fixturesHash.
|
|
175
|
+
modelFixture = _.cloneDeep(modelFixture);
|
|
176
|
+
// The Post model fixtures need a `published_at` date, where at least the seconds
|
|
177
|
+
// are different, otherwise `prev_post` and `next_post` helpers won't workd with
|
|
178
|
+
// them.
|
|
179
|
+
if (modelFixture.name === 'Post') {
|
|
180
|
+
_.forEach(modelFixture.entries, (post, index) => {
|
|
181
|
+
if (!post.published_at) {
|
|
182
|
+
post.published_at = moment().add(index, 'seconds');
|
|
270
183
|
}
|
|
271
184
|
});
|
|
272
|
-
}
|
|
273
|
-
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
188
|
+
let data = {};
|
|
189
|
+
|
|
190
|
+
// CASE: if id is specified, only query by id
|
|
191
|
+
if (entry.id) {
|
|
192
|
+
data.id = entry.id;
|
|
193
|
+
} else if (entry.slug) {
|
|
194
|
+
data.slug = entry.slug;
|
|
195
|
+
} else {
|
|
196
|
+
data = _.cloneDeep(entry);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (modelFixture.name === 'Post') {
|
|
200
|
+
data.status = 'all';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const found = await models[modelFixture.name].findOne(data, options);
|
|
204
|
+
if (!found) {
|
|
205
|
+
return models[modelFixture.name].add(entry, options);
|
|
206
|
+
}
|
|
274
207
|
});
|
|
208
|
+
|
|
209
|
+
return {expected: modelFixture.entries.length, done: _.compact(results).length};
|
|
275
210
|
};
|
|
276
211
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
212
|
+
/**
|
|
213
|
+
* ## Add Fixtures for Relation
|
|
214
|
+
* Takes a relation fixtures object, with a from, to and some entries and processes these
|
|
215
|
+
* into a sequence of promises, to get each fixture added.
|
|
216
|
+
*
|
|
217
|
+
* @param {{from, to, entries}} relationFixture
|
|
218
|
+
* @returns {Promise<any>}
|
|
219
|
+
*/
|
|
220
|
+
const addFixturesForRelation = async function addFixturesForRelation(relationFixture, options) {
|
|
221
|
+
const ops = [];
|
|
222
|
+
let max = 0;
|
|
223
|
+
|
|
224
|
+
const data = await fetchRelationData(relationFixture, options);
|
|
225
|
+
|
|
226
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
227
|
+
const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
|
|
228
|
+
|
|
229
|
+
// CASE: You add new fixtures e.g. a new role in a new release.
|
|
230
|
+
// As soon as an **older** migration script wants to add permissions for any resource, it iterates over the
|
|
231
|
+
// permissions for each role. But if the role does not exist yet, it won't find the matching db entry and breaks.
|
|
232
|
+
if (!fromItem) {
|
|
233
|
+
logging.warn('Skip: Target database entry not found for key: ' + key);
|
|
234
|
+
return Promise.resolve();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_.each(entry, (value, entryKey) => {
|
|
238
|
+
let toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
|
|
239
|
+
max += toItems.length;
|
|
240
|
+
|
|
241
|
+
// Remove any duplicates that already exist in the collection
|
|
242
|
+
toItems = _.reject(toItems, (item) => {
|
|
243
|
+
return fromItem
|
|
244
|
+
.related(relationFixture.from.relation)
|
|
245
|
+
.find((model) => {
|
|
246
|
+
const objectToMatch = matchObj(relationFixture.to.match, item);
|
|
247
|
+
return Object.keys(objectToMatch).every((keyToCheck) => {
|
|
248
|
+
return model.get(keyToCheck) === objectToMatch[keyToCheck];
|
|
249
|
+
});
|
|
296
250
|
});
|
|
297
|
-
}
|
|
298
251
|
});
|
|
252
|
+
|
|
253
|
+
if (toItems && toItems.length > 0) {
|
|
254
|
+
ops.push(function addRelationItems() {
|
|
255
|
+
return baseUtils.attach(
|
|
256
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
257
|
+
fromItem.id,
|
|
258
|
+
relationFixture.from.relation,
|
|
259
|
+
toItems,
|
|
260
|
+
options
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
299
264
|
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await sequence(ops);
|
|
268
|
+
return {expected: max, done: _(result).map('length').sum()};
|
|
269
|
+
};
|
|
300
270
|
|
|
301
|
-
|
|
271
|
+
const removeFixturesForModel = async function removeFixturesForModel(modelFixture, options) {
|
|
272
|
+
const results = await Promise.mapSeries(modelFixture.entries, async (entry) => {
|
|
273
|
+
const found = models[modelFixture.name].findOne(entry.id ? {id: entry.id} : entry, options);
|
|
274
|
+
if (found) {
|
|
275
|
+
return models[modelFixture.name].destroy(_.extend(options, {id: found.id}));
|
|
276
|
+
}
|
|
302
277
|
});
|
|
278
|
+
|
|
279
|
+
return {expected: modelFixture.entries.length, done: results.length};
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const removeFixturesForRelation = async function removeFixturesForRelation(relationFixture, options) {
|
|
283
|
+
const data = await fetchRelationData(relationFixture, options);
|
|
284
|
+
const ops = [];
|
|
285
|
+
|
|
286
|
+
_.each(relationFixture.entries, (entry, key) => {
|
|
287
|
+
const fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
|
|
288
|
+
|
|
289
|
+
_.each(entry, (value, entryKey) => {
|
|
290
|
+
const toItems = data.to.filter(matchFunc(relationFixture.to.match, entryKey, value));
|
|
291
|
+
|
|
292
|
+
if (toItems && toItems.length > 0) {
|
|
293
|
+
ops.push(function detachRelation() {
|
|
294
|
+
return baseUtils.detach(
|
|
295
|
+
models[relationFixture.from.Model || relationFixture.from.model],
|
|
296
|
+
fromItem.id,
|
|
297
|
+
relationFixture.from.relation,
|
|
298
|
+
toItems,
|
|
299
|
+
options
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return await sequence(ops);
|
|
303
307
|
};
|
|
304
308
|
|
|
305
309
|
module.exports = {
|
|
306
|
-
|
|
307
|
-
addFixturesForRelation: addFixturesForRelation,
|
|
310
|
+
// find
|
|
308
311
|
findModelFixtureEntry: findModelFixtureEntry,
|
|
309
312
|
findModelFixtures: findModelFixtures,
|
|
310
313
|
findRelationFixture: findRelationFixture,
|
|
311
314
|
findPermissionRelationsForObject: findPermissionRelationsForObject,
|
|
315
|
+
|
|
316
|
+
// add / remove
|
|
317
|
+
addFixturesForModel: addFixturesForModel,
|
|
318
|
+
addFixturesForRelation: addFixturesForRelation,
|
|
312
319
|
removeFixturesForModel: removeFixturesForModel,
|
|
313
320
|
removeFixturesForRelation: removeFixturesForRelation
|
|
314
321
|
};
|
|
@@ -390,14 +390,14 @@ module.exports = {
|
|
|
390
390
|
name: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
391
391
|
code: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
392
392
|
product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id'},
|
|
393
|
-
stripe_coupon_id: {type: 'string', maxlength: 255, nullable:
|
|
393
|
+
stripe_coupon_id: {type: 'string', maxlength: 255, nullable: true, unique: true},
|
|
394
394
|
interval: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['month', 'year']]}},
|
|
395
395
|
currency: {type: 'string', maxlength: 50, nullable: true},
|
|
396
396
|
discount_type: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['percent', 'amount']]}},
|
|
397
397
|
discount_amount: {type: 'integer', nullable: false},
|
|
398
398
|
duration: {type: 'string', maxlength: 50, nullable: false},
|
|
399
399
|
duration_in_months: {type: 'integer', nullable: true},
|
|
400
|
-
portal_title: {type: 'string', maxlength: 191, nullable:
|
|
400
|
+
portal_title: {type: 'string', maxlength: 191, nullable: true},
|
|
401
401
|
portal_description: {type: 'string', maxlength: 2000, nullable: true},
|
|
402
402
|
created_at: {type: 'dateTime', nullable: false},
|
|
403
403
|
updated_at: {type: 'dateTime', nullable: true}
|
|
@@ -533,7 +533,8 @@ module.exports = {
|
|
|
533
533
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
534
534
|
offer_id: {type: 'string', maxlength: 24, nullable: false, references: 'offers.id', cascadeDelete: true},
|
|
535
535
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
536
|
-
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id'}
|
|
536
|
+
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
|
|
537
|
+
created_at: {type: 'dateTime', nullable: false}
|
|
537
538
|
},
|
|
538
539
|
members_subscribe_events: {
|
|
539
540
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.darkgrey{color:#343f44}.midgrey{color:#738a94}.lightgrey{color:#e5eff5}.blue{color:#3eb0ef}.red{color:#f05230}.orange{color:#fecd35}.green{color:#a4d037}.darkgrey-hover:hover{color:#343f44}.midgrey-hover:hover{color:#738a94}.lightgrey-hover:hover{color:#e5eff5}.blue-hover:hover{color:#3eb0ef}.red-hover:hover{color:#f05230}.orange-hover:hover{color:#fecd35}.green-hover:hover{color:#a4d037}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:62.5%;letter-spacing:.2px;line-height:1.65;overflow:hidden}body,html{height:100%;width:100%}body{color:#343f44;font-size:1.4rem;overflow:auto;overflow-x:hidden}.gh-view{-ms-flex-positive:1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1}h1,h2{text-rendering:optimizeLegibility;color:#343f44;font-size:2.9rem;line-height:1.15em;margin:0 0 .3em;text-indent:-1px}@media (max-width:500px){h1{font-size:2.4rem}}.gh-input{-webkit-appearance:none;border:1px solid #d6e3eb;border-radius:4px;color:#4b5b62;display:block;font-size:1.6rem;font-weight:300;height:40px;line-height:1em;padding:10px 12px;transition:border-color .15s linear;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;width:100%}.gh-input:focus{border-color:#b4cbda;outline:0}.gh-btn{fill:#829aa8;-webkit-font-smoothing:subpixel-antialiased;border:1px solid #d6e3eb;border-radius:5px;color:#829aa8;display:inline-block;outline:none;text-decoration:none!important;text-shadow:0 1px 0 #fff;transition:all .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gh-btn span{border-radius:4px;display:block;font-size:1.3rem;font-weight:400;height:33px;letter-spacing:.2px;line-height:33px;padding:0 12px;text-align:center}.gh-btn:hover{border-color:#b4cbda}.gh-btn-hover-blue:hover{border-color:#3eb0ef;color:#3eb0ef}.gh-btn-blue{fill:#fff;background:linear-gradient(#3da1d6,#2288bf);border:0;box-shadow:0 1px 0 rgba(0,0,0,.12);color:#fff;padding:1px;text-shadow:0 -1px 0 rgba(0,0,0,.1);transition:none!important}.gh-btn-blue span{background:linear-gradient(#4ab6f0,#2fa5e4 60%,#2fa5e4 90%,#38a9e5);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.gh-btn-blue:active,.gh-btn-blue:focus{background:#1e78a9}.gh-btn-blue:active span,.gh-btn-blue:focus span{background:#29a0e0;box-shadow:none}.gh-btn-block{display:block;width:100%}.gh-input-icon{display:block;position:relative}.gh-input-icon svg{fill:color(var(--midgrey) l(15%));height:14px;left:10px;position:absolute;top:50%;transform:translateY(-7px);width:auto;z-index:2}.gh-input-icon input{padding-left:35px}.gh-app{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%;overflow:hidden}.gh-viewport{max-height:100%;overflow:hidden}.gh-main,.gh-viewport{-ms-flex-positive:1;display:-ms-flexbox;display:flex;flex-grow:1}.gh-main{background:#fff;overflow-y:auto;position:relative}.gh-flow{-ms-flex-positive:1;-ms-flex-direction:column;flex-direction:column;flex-grow:1;min-height:100%;overflow-y:auto}.gh-flow,.gh-flow-head{display:-ms-flexbox;display:flex}.gh-flow-head{-ms-flex-negative:0;-ms-flex-pack:justify;flex-shrink:0;justify-content:space-between;padding-bottom:20px;padding-top:4vh}.gh-flow-content-wrap{-ms-flex-positive:1;-ms-flex-negative:0;-ms-flex-pack:center;flex-grow:1;flex-shrink:0;justify-content:center;margin:0 5%;padding-bottom:8vh}.gh-flow-back,.gh-flow-content-wrap{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.gh-flow-back{border:1px solid transparent;border-radius:4px;color:#7d878a;font-weight:100;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;top:0;transition:all .3s ease}.gh-flow-back svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back:hover{border:1px solid #dae1e3}.gh-flow-back-plain{-ms-flex-align:center;align-items:center;color:#7d878a;display:-ms-flexbox;display:flex;font-weight:300;left:0;margin:0 0 0 3%;padding:2px 9px 2px 5px;position:absolute;text-decoration:none;top:0;transition:all .3s ease}.gh-flow-back-plain svg{height:12px;line-height:14px;margin-right:4px}.gh-flow-back-plain svg path{stroke:#7d878a;stroke-width:1.2px}.gh-flow-back-plain:hover{color:#15212a}.gh-flow-nav{-ms-flex:1;flex:1;position:relative}.gh-flow-content{color:#738a94;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;font-size:1.9rem;font-weight:100;line-height:1.5em;max-width:700px;text-align:center;width:100%}.gh-flow-content .gh-input-icon input{padding-left:35px}.gh-flow-content-unsubscribe{font-weight:300}@media (max-width:500px){.gh-flow-head-unsubscribe{padding-top:2.8vh}.gh-flow-content{font-size:4vw}.gh-flow-content-unsubscribe{font-size:1.8rem;line-height:1.6em}}.gh-flow-content header{margin:0 auto;max-width:520px}.gh-flow-content h1{font-size:4.2rem;font-weight:100}@media (max-width:600px){.gh-flow-content h1{font-size:7vw}}.gh-flow-content .gh-btn{display:block;margin:20px auto 0;max-width:400px}.gh-flow-content .form-group{margin-bottom:2.5rem}.gh-flow-content input{border:1px solid #dae1e3;font-size:1.6rem;font-weight:100;line-height:1.4em;padding:10px}.gh-flow-em{font-weight:500}.gh-signin{background:#f8fbfd;border:1px solid #dae1e3;border-radius:5px;margin:30px auto;max-width:400px;padding:40px;position:relative;text-align:left;width:100%}.gh-signin .form-group{margin-bottom:1.5rem}.gh-signin .gh-btn{margin:0}.error-content{flex-grow:1;justify-content:center;padding:8vw;user-select:text}.error-content,.error-details{align-items:center;display:flex}.error-details{margin-bottom:4rem}.error-ghost{height:115px;margin:15px}@media (max-width:630px){.error-ghost{display:none}}.error-code{color:#c5d2d9;font-size:10vw;font-weight:600;letter-spacing:-.4vw;line-height:.9em;margin:0}.error-description{border:none;color:#54666d;font-size:2.3rem;font-weight:300;line-height:1.3em;margin:0;padding:0}.error-message{align-items:center;display:flex;flex-direction:column;margin:15px}.error-message a{font-size:1.4rem;line-height:1;margin:8px 0}.error-link{background-color:transparent;color:#5ba4e5;text-decoration:none;transition:background .3s,color .3s}.error-stack{background-color:hsla(0,0%,100%,.3);margin:1rem auto;max-width:800px;padding:2rem}.error-stack-list{list-style-type:none;margin:0;padding:0}.error-stack-list li{display:block}.error-stack-list li:before{color:#bbb;content:"\21AA";display:inline-block;font-size:1.2rem;margin-right:.5rem}.error-stack-function{font-weight:700}
|
|
@@ -216,7 +216,7 @@ class ImageSize {
|
|
|
216
216
|
// get the storage readable filePath
|
|
217
217
|
filePath = this.storageUtils.getLocalFileStoragePath(imagePath);
|
|
218
218
|
|
|
219
|
-
return this.storage.getStorage()
|
|
219
|
+
return this.storage.getStorage('images')
|
|
220
220
|
.read({path: filePath})
|
|
221
221
|
.then((buf) => {
|
|
222
222
|
debug('Image fetched (storage):', filePath);
|
|
@@ -267,7 +267,7 @@ class ImageSize {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
const originalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
|
|
270
|
-
const originalImageExists = await this.storage.getStorage().exists(originalImagePath);
|
|
270
|
+
const originalImageExists = await this.storage.getStorage('images').exists(originalImagePath);
|
|
271
271
|
|
|
272
272
|
return this.getImageSizeFromStoragePath(originalImageExists ? originalImagePath : imagePath);
|
|
273
273
|
}
|
|
@@ -3,7 +3,6 @@ const errors = require('@tryghost/errors');
|
|
|
3
3
|
const logging = require('@tryghost/logging');
|
|
4
4
|
const config = require('../../shared/config');
|
|
5
5
|
const storage = require('../adapters/storage');
|
|
6
|
-
const imageTransform = require('@tryghost/image-transform');
|
|
7
6
|
|
|
8
7
|
let cardFactory;
|
|
9
8
|
let cards;
|
|
@@ -34,11 +33,13 @@ module.exports = {
|
|
|
34
33
|
siteUrl: config.get('url'),
|
|
35
34
|
imageOptimization: config.get('imageOptimization'),
|
|
36
35
|
canTransformImage(storagePath) {
|
|
36
|
+
const imageTransform = require('@tryghost/image-transform');
|
|
37
37
|
const {ext} = path.parse(storagePath);
|
|
38
38
|
|
|
39
|
+
// NOTE: the "saveRaw" check is smelly
|
|
39
40
|
return imageTransform.canTransformFiles()
|
|
40
41
|
&& imageTransform.canTransformFileExtension(ext)
|
|
41
|
-
&& typeof storage.getStorage().saveRaw === 'function';
|
|
42
|
+
&& typeof storage.getStorage('images').saveRaw === 'function';
|
|
42
43
|
}
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -3,13 +3,16 @@ const ghostBookshelf = require('./base');
|
|
|
3
3
|
|
|
4
4
|
const candidates = [];
|
|
5
5
|
|
|
6
|
-
_.each(ghostBookshelf.registry.models, (model) => {
|
|
7
|
-
candidates.push([model, model.prototype.tableName.replace(/s$/, '')]);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
6
|
const Action = ghostBookshelf.Model.extend({
|
|
11
7
|
tableName: 'actions',
|
|
12
8
|
|
|
9
|
+
initialize: function initialize() {
|
|
10
|
+
_.each(ghostBookshelf.registry.models, (model) => {
|
|
11
|
+
candidates.push([model, model.prototype.tableName.replace(/s$/, '')]);
|
|
12
|
+
});
|
|
13
|
+
this.constructor.__super__.initialize.apply(this, arguments);
|
|
14
|
+
},
|
|
15
|
+
|
|
13
16
|
actor() {
|
|
14
17
|
return this.morphTo('actor', ['actor_type', 'actor_id'], ...candidates);
|
|
15
18
|
},
|
|
@@ -19,15 +19,24 @@ module.exports = function (Bookshelf) {
|
|
|
19
19
|
const originalInsertSync = parentSync.insert;
|
|
20
20
|
const self = this;
|
|
21
21
|
|
|
22
|
-
// deep clone attrs to avoid modifying underlying model attributes by reference
|
|
23
22
|
parentSync.update = function update(attrs) {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
self._isWriting = true;
|
|
24
|
+
|
|
25
|
+
const originalPromise = originalUpdateSync.apply(this, [attrs]);
|
|
26
|
+
|
|
27
|
+
return originalPromise.finally(function () {
|
|
28
|
+
self._isWriting = false;
|
|
29
|
+
});
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
parentSync.insert = function insert(
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
parentSync.insert = function insert() {
|
|
33
|
+
self._isWriting = true;
|
|
34
|
+
|
|
35
|
+
const originalPromise = originalInsertSync.apply(this);
|
|
36
|
+
|
|
37
|
+
return originalPromise.finally(function () {
|
|
38
|
+
self._isWriting = false;
|
|
39
|
+
});
|
|
31
40
|
};
|
|
32
41
|
|
|
33
42
|
return parentSync;
|
|
@@ -42,6 +51,10 @@ module.exports = function (Bookshelf) {
|
|
|
42
51
|
|
|
43
52
|
// format date before writing to DB, bools work
|
|
44
53
|
format: function format(attrs) {
|
|
54
|
+
if (this._isWriting) {
|
|
55
|
+
attrs = this.formatOnWrite(attrs);
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
return this.fixDatesWhenSave(attrs);
|
|
46
59
|
},
|
|
47
60
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const _ = require('lodash');
|
|
6
|
+
const glob = require('glob');
|
|
6
7
|
|
|
7
8
|
// enable event listeners
|
|
8
9
|
require('./base/listeners');
|
|
@@ -12,55 +13,12 @@ require('./base/listeners');
|
|
|
12
13
|
*/
|
|
13
14
|
exports = module.exports;
|
|
14
15
|
|
|
15
|
-
const models = [
|
|
16
|
-
'permission',
|
|
17
|
-
'post',
|
|
18
|
-
'role',
|
|
19
|
-
'settings',
|
|
20
|
-
'custom-theme-setting',
|
|
21
|
-
'session',
|
|
22
|
-
'tag',
|
|
23
|
-
'tag-public',
|
|
24
|
-
'user',
|
|
25
|
-
'author',
|
|
26
|
-
'invite',
|
|
27
|
-
'webhook',
|
|
28
|
-
'integration',
|
|
29
|
-
'api-key',
|
|
30
|
-
'mobiledoc-revision',
|
|
31
|
-
'member',
|
|
32
|
-
'offer',
|
|
33
|
-
'offer-redemption',
|
|
34
|
-
'product',
|
|
35
|
-
'benefit',
|
|
36
|
-
'stripe-product',
|
|
37
|
-
'stripe-price',
|
|
38
|
-
'member-subscribe-event',
|
|
39
|
-
'member-paid-subscription-event',
|
|
40
|
-
'member-login-event',
|
|
41
|
-
'member-email-change-event',
|
|
42
|
-
'member-payment-event',
|
|
43
|
-
'member-status-event',
|
|
44
|
-
'member-product-event',
|
|
45
|
-
'member-analytic-event',
|
|
46
|
-
'posts-meta',
|
|
47
|
-
'member-stripe-customer',
|
|
48
|
-
'stripe-customer-subscription',
|
|
49
|
-
'email',
|
|
50
|
-
'email-batch',
|
|
51
|
-
'email-recipient',
|
|
52
|
-
'label',
|
|
53
|
-
'single-use-token',
|
|
54
|
-
'snippet',
|
|
55
|
-
// Action model MUST be loaded last as it loops through all of the registered models
|
|
56
|
-
// Please do not append items to this array.
|
|
57
|
-
'action'
|
|
58
|
-
];
|
|
59
|
-
|
|
60
16
|
function init() {
|
|
61
17
|
exports.Base = require('./base');
|
|
62
18
|
|
|
63
|
-
|
|
19
|
+
let modelsFiles = glob.sync('!(index).js', {cwd: __dirname});
|
|
20
|
+
modelsFiles.forEach((model) => {
|
|
21
|
+
const name = model.replace(/.js$/, '');
|
|
64
22
|
_.extend(exports, require('./' + name));
|
|
65
23
|
});
|
|
66
24
|
}
|
|
@@ -94,6 +94,11 @@ const Member = ghostBookshelf.Model.extend({
|
|
|
94
94
|
});
|
|
95
95
|
},
|
|
96
96
|
|
|
97
|
+
offerRedemptions() {
|
|
98
|
+
return this.hasMany('OfferRedemption', 'member_id', 'id')
|
|
99
|
+
.query('orderBy', 'created_at', 'DESC');
|
|
100
|
+
},
|
|
101
|
+
|
|
97
102
|
labels: function labels() {
|
|
98
103
|
return this.belongsToMany('Label', 'members_labels', 'member_id', 'label_id')
|
|
99
104
|
.withPivot('sort_order')
|
|
@@ -8,7 +8,6 @@ const limitService = require('../services/limits');
|
|
|
8
8
|
const tpl = require('@tryghost/tpl');
|
|
9
9
|
const errors = require('@tryghost/errors');
|
|
10
10
|
const security = require('@tryghost/security');
|
|
11
|
-
const {gravatar} = require('../lib/image');
|
|
12
11
|
const {pipeline} = require('@tryghost/promise');
|
|
13
12
|
const validatePassword = require('../lib/validate-password');
|
|
14
13
|
const permissions = require('../services/permissions');
|
|
@@ -191,6 +190,8 @@ User = ghostBookshelf.Model.extend({
|
|
|
191
190
|
// If the user's email is set & has changed & we are not importing
|
|
192
191
|
if (self.hasChanged('email') && self.get('email') && !options.importing) {
|
|
193
192
|
tasks.gravatar = (function lookUpGravatar() {
|
|
193
|
+
const {gravatar} = require('../lib/image');
|
|
194
|
+
|
|
194
195
|
return gravatar.lookup({
|
|
195
196
|
email: self.get('email')
|
|
196
197
|
}).then(function (response) {
|
package/core/server/overrides.js
CHANGED
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
process.env.BLUEBIRD_DEBUG = 0;
|
|
7
7
|
|
|
8
|
+
const luxon = require('luxon');
|
|
8
9
|
const moment = require('moment-timezone');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* force UTC
|
|
12
|
-
* - you can require moment or moment-timezone
|
|
13
|
+
* - old way: you can require moment or moment-timezone
|
|
14
|
+
* - new way: you should use Luxon - work is in progress to switch from moment.
|
|
15
|
+
*
|
|
13
16
|
* - you are allowed to use new Date() to instantiate datetime values for models, because they are transformed into UTC in the model layer
|
|
14
17
|
* - be careful when not working with models, every value from the native JS Date is local TZ
|
|
15
|
-
* - be careful when you work with date operations,
|
|
18
|
+
* - be careful when you work with date operations, therefore always wrap a date with our timezone library
|
|
16
19
|
*/
|
|
20
|
+
luxon.Settings.defaultZone = 'UTC';
|
|
17
21
|
moment.tz.setDefault('UTC');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const AdapterManager = require('@tryghost/adapter-manager');
|
|
2
2
|
const getAdapterServiceConfig = require('./config');
|
|
3
|
+
const resolveAdapterOptions = require('./options-resolver');
|
|
3
4
|
const config = require('../../../shared/config');
|
|
4
5
|
|
|
5
6
|
const adapterManager = new AdapterManager({
|
|
@@ -16,13 +17,16 @@ adapterManager.registerAdapter('scheduling', require('../../adapters/scheduling/
|
|
|
16
17
|
adapterManager.registerAdapter('sso', require('../../adapters/sso/Base'));
|
|
17
18
|
|
|
18
19
|
module.exports = {
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {String} name - one of 'storage', 'scheduling', 'sso' etc. Or can contain a "resource" extension like "storage:image"
|
|
23
|
+
* @returns {Object} instance of an adapter
|
|
24
|
+
*/
|
|
25
|
+
getAdapter(name) {
|
|
20
26
|
const adapterServiceConfig = getAdapterServiceConfig(config);
|
|
21
27
|
|
|
22
|
-
const
|
|
23
|
-
const activeAdapter = adapterSettings.active;
|
|
24
|
-
const activeAdapterConfig = adapterSettings[activeAdapter];
|
|
28
|
+
const {adapterType, adapterName, adapterConfig} = resolveAdapterOptions(name, adapterServiceConfig);
|
|
25
29
|
|
|
26
|
-
return adapterManager.getAdapter(adapterType,
|
|
30
|
+
return adapterManager.getAdapter(adapterType, adapterName, adapterConfig);
|
|
27
31
|
}
|
|
28
32
|
};
|