2018-04-06 19:19:45 +03:00
|
|
|
const Promise = require('bluebird'),
|
2017-12-12 00:47:46 +03:00
|
|
|
_ = require('lodash'),
|
|
|
|
uuid = require('uuid'),
|
|
|
|
crypto = require('crypto'),
|
2019-06-26 15:58:50 +03:00
|
|
|
keypair = require('keypair'),
|
🎨 ⏱ Cleanup / optimise the server.init() function (#7985)
refs #2182
* 🔥 Remove unused options from server init
- this is left over from old code and is now unused
* 🎨 Move knex-migrator check to db health
- Move complex check function into own module
- Call module from server/index.js
- This just improves the readability of server/index.js
* 🔥 Remove old comments
- These comments all make no sense now!
* 🎨 ⏱ Move model init out of promise chain
- Model.init() does not return a promise
- Therefore, we can move it to the top of the init function, outside of the promise change
- This should be a minor optimisation, and again improves readability /clarity of what's happening
* ✨ ⁉️ Move DBHash init / first run to Settings model
- this structure is left over from when we had code we executed on the first run of Ghost
- the implementation used the API to initialise one setting before populateDefaults is called
- this had lots of dependencies - the whole model, API, and permissions structure had to be initialised for it to work
- the new implementation is simpler, it captures the dbHash getting initialised during populateDefaults()
- it also adds an event, so we can do first-run code later if we really want to (or maybe apps can?!)
- perhaps this is hiding behaviour, and there's a nicer way to do it, but populateDefaults seems like a sane place to populate a default setting 😁
* ⏱ Optimise require order so config is first
- the first require to config will cause the files to be read etc
- this ensures that it happens early, and isn't confusingly timed as part of loading a different module
* 🎨 Simplify settings model changes
2017-02-17 19:44:34 +03:00
|
|
|
ghostBookshelf = require('./base'),
|
2017-12-12 00:47:46 +03:00
|
|
|
common = require('../lib/common'),
|
|
|
|
validation = require('../data/validation'),
|
2018-04-06 19:19:45 +03:00
|
|
|
internalContext = {context: {internal: true}};
|
🎨 ⏱ Cleanup / optimise the server.init() function (#7985)
refs #2182
* 🔥 Remove unused options from server init
- this is left over from old code and is now unused
* 🎨 Move knex-migrator check to db health
- Move complex check function into own module
- Call module from server/index.js
- This just improves the readability of server/index.js
* 🔥 Remove old comments
- These comments all make no sense now!
* 🎨 ⏱ Move model init out of promise chain
- Model.init() does not return a promise
- Therefore, we can move it to the top of the init function, outside of the promise change
- This should be a minor optimisation, and again improves readability /clarity of what's happening
* ✨ ⁉️ Move DBHash init / first run to Settings model
- this structure is left over from when we had code we executed on the first run of Ghost
- the implementation used the API to initialise one setting before populateDefaults is called
- this had lots of dependencies - the whole model, API, and permissions structure had to be initialised for it to work
- the new implementation is simpler, it captures the dbHash getting initialised during populateDefaults()
- it also adds an event, so we can do first-run code later if we really want to (or maybe apps can?!)
- perhaps this is hiding behaviour, and there's a nicer way to do it, but populateDefaults seems like a sane place to populate a default setting 😁
* ⏱ Optimise require order so config is first
- the first require to config will cause the files to be read etc
- this ensures that it happens early, and isn't confusingly timed as part of loading a different module
* 🎨 Simplify settings model changes
2017-02-17 19:44:34 +03:00
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
let Settings, defaultSettings;
|
2013-09-02 05:49:08 +04:00
|
|
|
|
2019-07-05 10:30:29 +03:00
|
|
|
const doBlock = fn => fn();
|
|
|
|
|
|
|
|
const getMembersKey = doBlock(() => {
|
|
|
|
let UNO_KEYPAIRINO;
|
|
|
|
return function getMembersKey(type) {
|
|
|
|
if (!UNO_KEYPAIRINO) {
|
|
|
|
UNO_KEYPAIRINO = keypair({bits: 1024});
|
|
|
|
}
|
|
|
|
return UNO_KEYPAIRINO[type];
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2013-09-02 05:49:08 +04:00
|
|
|
// For neatness, the defaults file is split into categories.
|
|
|
|
// It's much easier for us to work with it as a single level
|
|
|
|
// instead of iterating those categories every time
|
|
|
|
function parseDefaultSettings() {
|
2016-01-25 20:50:04 +03:00
|
|
|
var defaultSettingsInCategories = require('../data/schema/').defaultSettings,
|
🎨 ⏱ Cleanup / optimise the server.init() function (#7985)
refs #2182
* 🔥 Remove unused options from server init
- this is left over from old code and is now unused
* 🎨 Move knex-migrator check to db health
- Move complex check function into own module
- Call module from server/index.js
- This just improves the readability of server/index.js
* 🔥 Remove old comments
- These comments all make no sense now!
* 🎨 ⏱ Move model init out of promise chain
- Model.init() does not return a promise
- Therefore, we can move it to the top of the init function, outside of the promise change
- This should be a minor optimisation, and again improves readability /clarity of what's happening
* ✨ ⁉️ Move DBHash init / first run to Settings model
- this structure is left over from when we had code we executed on the first run of Ghost
- the implementation used the API to initialise one setting before populateDefaults is called
- this had lots of dependencies - the whole model, API, and permissions structure had to be initialised for it to work
- the new implementation is simpler, it captures the dbHash getting initialised during populateDefaults()
- it also adds an event, so we can do first-run code later if we really want to (or maybe apps can?!)
- perhaps this is hiding behaviour, and there's a nicer way to do it, but populateDefaults seems like a sane place to populate a default setting 😁
* ⏱ Optimise require order so config is first
- the first require to config will cause the files to be read etc
- this ensures that it happens early, and isn't confusingly timed as part of loading a different module
* 🎨 Simplify settings model changes
2017-02-17 19:44:34 +03:00
|
|
|
defaultSettingsFlattened = {},
|
|
|
|
dynamicDefault = {
|
2019-07-05 10:30:29 +03:00
|
|
|
db_hash: () => uuid.v4(),
|
|
|
|
public_hash: () => crypto.randomBytes(15).toString('hex'),
|
Updated theme layer to use members-ssr (#10676)
* Removed support for cookies in members auth middleware
no-issue
The members middleware will no longer be supporting cookies, the cookie
will be handled by a new middleware specific for serverside rendering,
more informations can be found here:
https://paper.dropbox.com/doc/Members-Auth-II-4WP4vF6coMqDYbSMIajo5
* Removed members auth middleware from site app
no-issue
The site app no longer needs the members auth middleware as it doesn't
support cookies, and will be replaced by ssr specific middleware.
https://paper.dropbox.com/doc/Members-Auth-II-4WP4vF6coMqDYbSMIajo5
* Added comment for session_secret setting
no-issue
We are going to have multiple concepts of sessions, so adding a comment
here to be specific that this is for the Ghost Admin client
* Added theme_session_secret setting dynamic default
no-issue
Sessions for the theme layer will be signed, so we generate a random hex
string to use as a signing key
* Added getPublicConfig method
* Replaced export of httpHandler with POJO apiInstance
no-issue
This is mainly to reduce the public api, so it's easier to document.
* Renamed memberUserObject -> members
no-issue
Simplifies the interface, and is more inline with what we would want to export as an api library.
* Removed use of require options inside members
no-issue
This was too tight of a coupling between Ghost and Members
* Simplified apiInstance definition
no-issue
* Added getMember method to members api
* Added MembersSSR instance to members service
* Wired up routes for members ssr
* Updated members auth middleware to use getPublicConfig
* Removed publicKey static export from members service
* Used real session secret
no-issue
* Added DELETE /members/ssr handler
no-issue
This allows users to log out of the theme layer
* Fixed missing code property
no-issue
Ignition uses the statusCode property to forward status codes to call sites
* Removed superfluous error middleware
no-issue
Before we used generic JWT middleware which would reject, now the
middleware catches it's own error and doesn't error, thus this
middleware is unecessary.
* Removed console.logs
no-issue
* Updated token expirty to hardcoded 20 minutes
no-issue
This returns to our previous state of using short lived tokens, both for
security and simplicity.
* Removed hardcoded default member settings
no-issue
This is no longer needed, as defaults are in default-settings.json
* Removed stripe from default payment processor
no-issue
* Exported `getSiteUrl` method from url utils
no-issue
This keeps inline with newer naming conventions
* Updated how audience access control works
no-issue
Rather than being passed a function, members api now receives an object
which describes which origins have access to which audiences, and how
long those tokens should be allowed to work for. It also allows syntax
for default tokens where audience === origin requesting it. This can be
set to undefined or null to disable this functionality.
{
"http://site.com": {
"http://site.com": {
tokenLength: '5m'
},
"http://othersite.com": {
tokenLength: '1h'
}
},
"*": {
tokenLength: '30m'
}
}
* Updated members service to use access control feature
no-issue
This also cleans up a lot of unecessary variable definitions, and some
other minor cleanups.
* Added status code to auth pages html response
no-issue
This was missing, probably default but better to be explicit
* Updated gateway to have membersApiUrl from config
no-issue
Previously we were parsing the url, this was not very safe as we can
have Ghost hosted on a subdomain, and this would have failed.
* Added issuer to public config for members
no-issue
This can be used to request SSR tokens in the client
* Fixed path for gateway bundle
no-issue
* Updated settings model tests
no-issue
* Revert "Removed stripe from default payment processor"
This reverts commit 1d88d9b6d73a10091070bcc1b7f5779d071c7845.
* Revert "Removed hardcoded default member settings"
This reverts commit 9d899048ba7d4b272b9ac65a95a52af66b30914a.
* Installed @tryghost/members-ssr
* Fixed tests for settings model
2019-04-16 17:50:25 +03:00
|
|
|
// @TODO: session_secret would ideally be named "admin_session_secret"
|
2019-07-05 10:30:29 +03:00
|
|
|
session_secret: () => crypto.randomBytes(32).toString('hex'),
|
|
|
|
members_session_secret: () => crypto.randomBytes(32).toString('hex'),
|
|
|
|
theme_session_secret: () => crypto.randomBytes(32).toString('hex'),
|
|
|
|
members_public_key: () => getMembersKey('public'),
|
|
|
|
members_private_key: () => getMembersKey('private')
|
🎨 ⏱ Cleanup / optimise the server.init() function (#7985)
refs #2182
* 🔥 Remove unused options from server init
- this is left over from old code and is now unused
* 🎨 Move knex-migrator check to db health
- Move complex check function into own module
- Call module from server/index.js
- This just improves the readability of server/index.js
* 🔥 Remove old comments
- These comments all make no sense now!
* 🎨 ⏱ Move model init out of promise chain
- Model.init() does not return a promise
- Therefore, we can move it to the top of the init function, outside of the promise change
- This should be a minor optimisation, and again improves readability /clarity of what's happening
* ✨ ⁉️ Move DBHash init / first run to Settings model
- this structure is left over from when we had code we executed on the first run of Ghost
- the implementation used the API to initialise one setting before populateDefaults is called
- this had lots of dependencies - the whole model, API, and permissions structure had to be initialised for it to work
- the new implementation is simpler, it captures the dbHash getting initialised during populateDefaults()
- it also adds an event, so we can do first-run code later if we really want to (or maybe apps can?!)
- perhaps this is hiding behaviour, and there's a nicer way to do it, but populateDefaults seems like a sane place to populate a default setting 😁
* ⏱ Optimise require order so config is first
- the first require to config will cause the files to be read etc
- this ensures that it happens early, and isn't confusingly timed as part of loading a different module
* 🎨 Simplify settings model changes
2017-02-17 19:44:34 +03:00
|
|
|
};
|
2013-09-02 05:49:08 +04:00
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
_.each(defaultSettingsInCategories, function each(settings, categoryName) {
|
|
|
|
_.each(settings, function each(setting, settingName) {
|
2013-09-02 05:49:08 +04:00
|
|
|
setting.type = categoryName;
|
|
|
|
setting.key = settingName;
|
2019-07-05 10:30:29 +03:00
|
|
|
|
|
|
|
setting.getDefaultValue = function getDefaultValue() {
|
|
|
|
const getDynamicDefault = dynamicDefault[setting.key];
|
|
|
|
if (getDynamicDefault) {
|
|
|
|
return getDynamicDefault();
|
|
|
|
} else {
|
|
|
|
return setting.defaultValue;
|
|
|
|
}
|
|
|
|
};
|
2014-07-28 01:04:58 +04:00
|
|
|
|
2013-09-02 05:49:08 +04:00
|
|
|
defaultSettingsFlattened[settingName] = setting;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return defaultSettingsFlattened;
|
|
|
|
}
|
2014-06-17 19:36:47 +04:00
|
|
|
|
|
|
|
function getDefaultSettings() {
|
|
|
|
if (!defaultSettings) {
|
|
|
|
defaultSettings = parseDefaultSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultSettings;
|
|
|
|
}
|
2013-06-08 09:03:55 +04:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
// Each setting is saved as a separate row in the database,
|
|
|
|
// but the overlying API treats them as a single key:value mapping
|
2013-09-23 02:20:08 +04:00
|
|
|
Settings = ghostBookshelf.Model.extend({
|
2013-08-25 14:49:31 +04:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
tableName: 'settings',
|
2013-08-25 14:49:31 +04:00
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
defaults: function defaults() {
|
2013-06-25 15:43:15 +04:00
|
|
|
return {
|
2013-09-14 22:04:41 +04:00
|
|
|
type: 'core'
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
2013-08-25 14:49:31 +04:00
|
|
|
},
|
|
|
|
|
2017-07-21 11:58:58 +03:00
|
|
|
emitChange: function emitChange(event, options) {
|
2018-04-06 19:19:45 +03:00
|
|
|
const eventToTrigger = 'settings' + '.' + event;
|
|
|
|
ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
|
2015-06-15 11:36:01 +03:00
|
|
|
},
|
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
onDestroyed: function onDestroyed(model, options) {
|
2019-02-07 12:59:37 +03:00
|
|
|
ghostBookshelf.Model.prototype.onDestroyed.apply(this, arguments);
|
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
model.emitChange('deleted', options);
|
|
|
|
model.emitChange(model._previousAttributes.key + '.' + 'deleted', options);
|
2016-10-14 15:37:01 +03:00
|
|
|
},
|
2015-06-15 11:36:01 +03:00
|
|
|
|
2017-07-21 11:58:58 +03:00
|
|
|
onCreated: function onCreated(model, response, options) {
|
2019-02-07 12:59:37 +03:00
|
|
|
ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
|
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
model.emitChange('added', options);
|
2017-07-21 11:58:58 +03:00
|
|
|
model.emitChange(model.attributes.key + '.' + 'added', options);
|
2016-10-14 15:37:01 +03:00
|
|
|
},
|
|
|
|
|
2017-07-21 11:58:58 +03:00
|
|
|
onUpdated: function onUpdated(model, response, options) {
|
2019-02-07 12:59:37 +03:00
|
|
|
ghostBookshelf.Model.prototype.onUpdated.apply(this, arguments);
|
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
model.emitChange('edited', options);
|
2017-07-21 11:58:58 +03:00
|
|
|
model.emitChange(model.attributes.key + '.' + 'edited', options);
|
2015-06-15 11:36:01 +03:00
|
|
|
},
|
|
|
|
|
2016-10-14 15:37:01 +03:00
|
|
|
onValidate: function onValidate() {
|
2018-02-16 02:49:15 +03:00
|
|
|
var self = this;
|
2014-07-10 02:11:04 +04:00
|
|
|
|
2018-02-16 02:49:15 +03:00
|
|
|
return ghostBookshelf.Model.prototype.onValidate.apply(this, arguments)
|
|
|
|
.then(function then() {
|
|
|
|
return validation.validateSettings(getDefaultSettings(), self);
|
|
|
|
});
|
2019-03-06 14:56:26 +03:00
|
|
|
},
|
|
|
|
|
2019-03-07 14:17:21 +03:00
|
|
|
format() {
|
|
|
|
const attrs = ghostBookshelf.Model.prototype.format.apply(this, arguments);
|
|
|
|
|
|
|
|
// @NOTE: type TEXT will transform boolean to "0"
|
|
|
|
if (_.isBoolean(attrs.value)) {
|
|
|
|
attrs.value = attrs.value.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrs;
|
|
|
|
},
|
|
|
|
|
2019-03-06 14:56:26 +03:00
|
|
|
parse() {
|
|
|
|
const attrs = ghostBookshelf.Model.prototype.parse.apply(this, arguments);
|
|
|
|
|
|
|
|
// transform "0" to false
|
|
|
|
// transform "false" to false
|
|
|
|
if (attrs.value === '0' || attrs.value === '1') {
|
|
|
|
attrs.value = !!+attrs.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attrs.value === 'false' || attrs.value === 'true') {
|
|
|
|
attrs.value = JSON.parse(attrs.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrs;
|
2013-06-25 15:43:15 +04:00
|
|
|
}
|
|
|
|
}, {
|
2016-06-03 11:06:18 +03:00
|
|
|
findOne: function (data, options) {
|
|
|
|
if (_.isEmpty(data)) {
|
|
|
|
options = data;
|
|
|
|
}
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
// Allow for just passing the key instead of attributes
|
2016-06-03 11:06:18 +03:00
|
|
|
if (!_.isObject(data)) {
|
|
|
|
data = {key: data};
|
2013-06-15 18:10:30 +04:00
|
|
|
}
|
2016-06-03 11:06:18 +03:00
|
|
|
|
|
|
|
return Promise.resolve(ghostBookshelf.Model.findOne.call(this, data, options));
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-06-08 09:03:55 +04:00
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
edit: function (data, unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'edit'),
|
|
|
|
self = this;
|
2014-04-03 17:03:09 +04:00
|
|
|
|
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
|
|
|
if (!Array.isArray(data)) {
|
|
|
|
data = [data];
|
2013-06-08 09:03:55 +04:00
|
|
|
}
|
2014-04-03 17:03:09 +04:00
|
|
|
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.map(data, function (item) {
|
2013-06-25 15:43:15 +04:00
|
|
|
// Accept an array of models as input
|
2017-12-12 00:47:46 +03:00
|
|
|
if (item.toJSON) {
|
|
|
|
item = item.toJSON();
|
|
|
|
}
|
2014-04-28 03:28:50 +04:00
|
|
|
if (!(_.isString(item.key) && item.key.length > 0)) {
|
2017-12-12 00:47:46 +03:00
|
|
|
return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.models.settings.valueCannotBeBlank')}));
|
2014-04-28 03:28:50 +04:00
|
|
|
}
|
2014-05-06 05:45:08 +04:00
|
|
|
|
|
|
|
item = self.filterData(item);
|
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
return Settings.forge({key: item.key}).fetch(options).then(function then(setting) {
|
2013-09-02 05:49:08 +04:00
|
|
|
if (setting) {
|
2016-06-03 11:06:18 +03:00
|
|
|
// it's allowed to edit all attributes in case of importing/migrating
|
|
|
|
if (options.importing) {
|
2017-10-31 18:47:30 +03:00
|
|
|
return setting.save(item, options);
|
|
|
|
} else {
|
|
|
|
// If we have a value, set it.
|
2019-07-05 14:40:43 +03:00
|
|
|
if (Object.prototype.hasOwnProperty.call(item, 'value')) {
|
2017-10-31 18:47:30 +03:00
|
|
|
setting.set('value', item.value);
|
|
|
|
}
|
|
|
|
// Internal context can overwrite type (for fixture migrations)
|
2019-07-05 14:40:43 +03:00
|
|
|
if (options.context && options.context.internal && Object.prototype.hasOwnProperty.call(item, 'type')) {
|
2017-10-31 18:47:30 +03:00
|
|
|
setting.set('type', item.type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If anything has changed, save the updated model
|
|
|
|
if (setting.hasChanged()) {
|
|
|
|
return setting.save(null, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
return setting;
|
2016-06-03 11:06:18 +03:00
|
|
|
}
|
2013-09-02 05:49:08 +04:00
|
|
|
}
|
2014-04-03 17:03:09 +04:00
|
|
|
|
2017-12-12 00:47:46 +03:00
|
|
|
return Promise.reject(new common.errors.NotFoundError({message: common.i18n.t('errors.models.settings.unableToFindSetting', {key: item.key})}));
|
2016-10-04 18:33:43 +03:00
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
});
|
2013-09-02 05:49:08 +04:00
|
|
|
},
|
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
populateDefaults: function populateDefaults(unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'populateDefaults'),
|
|
|
|
self = this;
|
2017-03-03 01:00:01 +03:00
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
if (!options.context) {
|
|
|
|
options.context = internalContext.context;
|
|
|
|
}
|
2016-07-14 13:59:42 +03:00
|
|
|
|
2017-03-03 01:00:01 +03:00
|
|
|
return this
|
|
|
|
.findAll(options)
|
|
|
|
.then(function checkAllSettings(allSettings) {
|
2017-12-12 00:47:46 +03:00
|
|
|
var usedKeys = allSettings.models.map(function mapper(setting) {
|
|
|
|
return setting.get('key');
|
|
|
|
}),
|
2017-03-03 01:00:01 +03:00
|
|
|
insertOperations = [];
|
2016-07-14 13:59:42 +03:00
|
|
|
|
2017-03-03 01:00:01 +03:00
|
|
|
_.each(getDefaultSettings(), function forEachDefault(defaultSetting, defaultSettingKey) {
|
|
|
|
var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1;
|
|
|
|
if (isMissingFromDB) {
|
2019-07-05 10:30:29 +03:00
|
|
|
defaultSetting.value = defaultSetting.getDefaultValue();
|
2017-03-03 01:00:01 +03:00
|
|
|
insertOperations.push(Settings.forge(defaultSetting).save(null, options));
|
|
|
|
}
|
|
|
|
});
|
2013-09-02 05:49:08 +04:00
|
|
|
|
2017-03-03 01:00:01 +03:00
|
|
|
if (insertOperations.length > 0) {
|
|
|
|
return Promise.all(insertOperations).then(function fetchAllToReturn() {
|
|
|
|
return self.findAll(options);
|
|
|
|
});
|
2013-09-02 05:49:08 +04:00
|
|
|
}
|
|
|
|
|
2017-03-03 01:00:01 +03:00
|
|
|
return allSettings;
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
}
|
|
|
|
});
|
2013-06-08 09:03:55 +04:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
module.exports = {
|
2014-07-13 15:17:18 +04:00
|
|
|
Settings: ghostBookshelf.model('Settings', Settings)
|
2013-09-02 05:49:08 +04:00
|
|
|
};
|