mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-09 04:31:17 +03:00
00f95e7328
closes #10060
- Implemented scheduling for posts and pages
- Added cache invalidation when scheduling
- Refactored admin token eneration function to accept existing key as parameter in tests
- Added Ghost Scheduler Integration fixture
- Added fixture for permissions for post publish action
- Migrated getScheduled method to v2
- Did not add support for 'from' and 'to' parameters as they were not used by DefaultScheduler
- This method needs rethinking in a long run as it's an ugly hack and should rather become proper endpoint that returns JSON data instead of models
- Removed unused auth middleware from v2 routes
- Added internal scheduler role
- Implemetnted transactions in v2 frame
- This takes into account scenario mentioned in c93f03b87e
- Specifically:
>if two queries happening in a transaction we have to signalise
knex/mysql that we select for an update
otherwise the following case happens:
you fetch posts for an update
a user requests comes in and updates the post (e.g. sets title to "X")
you update the fetched posts, title would get overriden to the old one
203 lines
5.4 KiB
JavaScript
203 lines
5.4 KiB
JavaScript
const models = require('../../models');
|
|
const common = require('../../lib/common');
|
|
const urlUtils = require('../../lib/url-utils');
|
|
const allowedIncludes = ['tags', 'authors', 'authors.roles'];
|
|
const unsafeAttrs = ['status', 'authors'];
|
|
|
|
module.exports = {
|
|
docName: 'posts',
|
|
browse: {
|
|
options: [
|
|
'include',
|
|
'filter',
|
|
'fields',
|
|
'formats',
|
|
'limit',
|
|
'order',
|
|
'page',
|
|
'debug',
|
|
'absolute_urls'
|
|
],
|
|
validation: {
|
|
options: {
|
|
include: {
|
|
values: allowedIncludes
|
|
},
|
|
formats: {
|
|
values: models.Post.allowedFormats
|
|
}
|
|
}
|
|
},
|
|
permissions: {
|
|
unsafeAttrs: unsafeAttrs
|
|
},
|
|
query(frame) {
|
|
return models.Post.findPage(frame.options);
|
|
}
|
|
},
|
|
|
|
read: {
|
|
options: [
|
|
'include',
|
|
'fields',
|
|
'formats',
|
|
'debug',
|
|
'absolute_urls',
|
|
// NOTE: only for internal context
|
|
'forUpdate',
|
|
'transacting'
|
|
],
|
|
data: [
|
|
'id',
|
|
'slug',
|
|
'uuid'
|
|
],
|
|
validation: {
|
|
options: {
|
|
include: {
|
|
values: allowedIncludes
|
|
},
|
|
formats: {
|
|
values: models.Post.allowedFormats
|
|
}
|
|
}
|
|
},
|
|
permissions: {
|
|
unsafeAttrs: unsafeAttrs
|
|
},
|
|
query(frame) {
|
|
return models.Post.findOne(frame.data, frame.options)
|
|
.then((model) => {
|
|
if (!model) {
|
|
throw new common.errors.NotFoundError({
|
|
message: common.i18n.t('errors.api.posts.postNotFound')
|
|
});
|
|
}
|
|
|
|
return model;
|
|
});
|
|
}
|
|
},
|
|
|
|
add: {
|
|
statusCode: 201,
|
|
headers: {},
|
|
options: [
|
|
'include',
|
|
'source'
|
|
],
|
|
validation: {
|
|
options: {
|
|
include: {
|
|
values: allowedIncludes
|
|
},
|
|
source: {
|
|
values: ['html']
|
|
}
|
|
}
|
|
},
|
|
permissions: {
|
|
unsafeAttrs: unsafeAttrs
|
|
},
|
|
query(frame) {
|
|
return models.Post.add(frame.data.posts[0], frame.options)
|
|
.then((model) => {
|
|
if (model.get('status') !== 'published') {
|
|
this.headers.cacheInvalidate = false;
|
|
} else {
|
|
this.headers.cacheInvalidate = true;
|
|
}
|
|
|
|
return model;
|
|
});
|
|
}
|
|
},
|
|
|
|
edit: {
|
|
headers: {},
|
|
options: [
|
|
'include',
|
|
'id',
|
|
'source',
|
|
// NOTE: only for internal context
|
|
'forUpdate',
|
|
'transacting'
|
|
],
|
|
validation: {
|
|
options: {
|
|
include: {
|
|
values: allowedIncludes
|
|
},
|
|
id: {
|
|
required: true
|
|
},
|
|
source: {
|
|
values: ['html']
|
|
}
|
|
}
|
|
},
|
|
permissions: {
|
|
unsafeAttrs: unsafeAttrs
|
|
},
|
|
query(frame) {
|
|
return models.Post.edit(frame.data.posts[0], frame.options)
|
|
.then((model) => {
|
|
if (
|
|
model.get('status') === 'published' && model.wasChanged() ||
|
|
model.get('status') === 'draft' && model.previous('status') === 'published'
|
|
) {
|
|
this.headers.cacheInvalidate = true;
|
|
} else if (
|
|
model.get('status') === 'draft' && model.previous('status') !== 'published' ||
|
|
model.get('status') === 'scheduled' && model.wasChanged()
|
|
) {
|
|
this.headers.cacheInvalidate = {
|
|
value: urlUtils.urlFor({
|
|
relativeUrl: urlUtils.urlJoin('/p', model.get('uuid'), '/')
|
|
})
|
|
};
|
|
} else {
|
|
this.headers.cacheInvalidate = false;
|
|
}
|
|
|
|
return model;
|
|
});
|
|
}
|
|
},
|
|
|
|
destroy: {
|
|
statusCode: 204,
|
|
headers: {
|
|
cacheInvalidate: true
|
|
},
|
|
options: [
|
|
'include',
|
|
'id'
|
|
],
|
|
validation: {
|
|
options: {
|
|
include: {
|
|
values: allowedIncludes
|
|
},
|
|
id: {
|
|
required: true
|
|
}
|
|
}
|
|
},
|
|
permissions: {
|
|
unsafeAttrs: unsafeAttrs
|
|
},
|
|
query(frame) {
|
|
frame.options.require = true;
|
|
|
|
return models.Post.destroy(frame.options)
|
|
.return(null)
|
|
.catch(models.Post.NotFoundError, () => {
|
|
throw new common.errors.NotFoundError({
|
|
message: common.i18n.t('errors.api.posts.postNotFound')
|
|
});
|
|
});
|
|
}
|
|
}
|
|
};
|