mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Separated pages & posts in Admin API v2 (#10494)
refs #10438, refs #10106 * Renamed existing pages ctrl * Splitted posts & pages for Admin API v2 * Added pages JSON input schema for Admin API v2 * Removed single author for Content & Admin API v2 - single author is not documented - single author usage is deprecated in v0.1 - single author usage is removed in API v2 * Splitted posts & postsPublic controller for v2 * Removed requirement to send `status=all` from Admin API v2 * Removed `status` option from pages Content API v2 * Removed `status` options from Users Admin API v2
This commit is contained in:
parent
cf8622ea99
commit
0a70226128
37 changed files with 1375 additions and 150 deletions
|
@ -19,6 +19,10 @@ module.exports = {
|
|||
return require('./session');
|
||||
},
|
||||
|
||||
get pagesPublic() {
|
||||
return shared.pipeline(require('./pages-public'), localUtils);
|
||||
},
|
||||
|
||||
get pages() {
|
||||
return shared.pipeline(require('./pages'), localUtils);
|
||||
},
|
||||
|
@ -43,6 +47,10 @@ module.exports = {
|
|||
return shared.pipeline(require('./posts'), localUtils);
|
||||
},
|
||||
|
||||
get postsPublic() {
|
||||
return shared.pipeline(require('./posts-public'), localUtils);
|
||||
},
|
||||
|
||||
get invites() {
|
||||
return shared.pipeline(require('./invites'), localUtils);
|
||||
},
|
||||
|
|
73
core/server/api/v2/pages-public.js
Normal file
73
core/server/api/v2/pages-public.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const common = require('../../lib/common');
|
||||
const models = require('../../models');
|
||||
const ALLOWED_INCLUDES = ['tags', 'authors'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'pages',
|
||||
|
||||
browse: {
|
||||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'fields',
|
||||
'formats',
|
||||
'absolute_urls',
|
||||
'page',
|
||||
'limit',
|
||||
'order',
|
||||
'debug'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
},
|
||||
formats: {
|
||||
values: models.Post.allowedFormats
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Post.findPage(frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
read: {
|
||||
options: [
|
||||
'include',
|
||||
'fields',
|
||||
'formats',
|
||||
'debug',
|
||||
'absolute_urls'
|
||||
],
|
||||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'uuid'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
},
|
||||
formats: {
|
||||
values: models.Post.allowedFormats
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
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.pages.pageNotFound')
|
||||
});
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
const common = require('../../lib/common');
|
||||
const models = require('../../models');
|
||||
const ALLOWED_INCLUDES = ['author', 'tags', 'authors', 'authors.roles'];
|
||||
const common = require('../../lib/common');
|
||||
const urlService = require('../../services/url');
|
||||
const ALLOWED_INCLUDES = ['tags', 'authors', 'authors.roles'];
|
||||
const UNSAFE_ATTRS = ['status', 'authors'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'pages',
|
||||
|
@ -8,14 +10,13 @@ module.exports = {
|
|||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'status',
|
||||
'fields',
|
||||
'formats',
|
||||
'absolute_urls',
|
||||
'page',
|
||||
'limit',
|
||||
'order',
|
||||
'debug'
|
||||
'page',
|
||||
'debug',
|
||||
'absolute_urls'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
|
@ -27,7 +28,10 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
permissions: {
|
||||
docName: 'posts',
|
||||
unsafeAttrs: UNSAFE_ATTRS
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.findPage(frame.options);
|
||||
}
|
||||
|
@ -37,7 +41,6 @@ module.exports = {
|
|||
options: [
|
||||
'include',
|
||||
'fields',
|
||||
'status',
|
||||
'formats',
|
||||
'debug',
|
||||
'absolute_urls'
|
||||
|
@ -45,7 +48,6 @@ module.exports = {
|
|||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'status',
|
||||
'uuid'
|
||||
],
|
||||
validation: {
|
||||
|
@ -58,7 +60,10 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
permissions: {
|
||||
docName: 'posts',
|
||||
unsafeAttrs: UNSAFE_ATTRS
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.findOne(frame.data, frame.options)
|
||||
.then((model) => {
|
||||
|
@ -71,5 +76,113 @@ module.exports = {
|
|||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
add: {
|
||||
statusCode: 201,
|
||||
headers: {},
|
||||
options: [
|
||||
'include'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
docName: 'posts',
|
||||
unsafeAttrs: UNSAFE_ATTRS
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.add(frame.data.pages[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'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
docName: 'posts',
|
||||
unsafeAttrs: UNSAFE_ATTRS
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.edit(frame.data.pages[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') {
|
||||
this.headers.cacheInvalidate = {
|
||||
value: urlService.utils.urlFor({
|
||||
relativeUrl: urlService.utils.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: ALLOWED_INCLUDES
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
docName: 'posts',
|
||||
unsafeAttrs: UNSAFE_ATTRS
|
||||
},
|
||||
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.pages.pageNotFound')
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
73
core/server/api/v2/posts-public.js
Normal file
73
core/server/api/v2/posts-public.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const models = require('../../models');
|
||||
const common = require('../../lib/common');
|
||||
const allowedIncludes = ['tags', '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: true,
|
||||
query(frame) {
|
||||
return models.Post.findPage(frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
read: {
|
||||
options: [
|
||||
'include',
|
||||
'fields',
|
||||
'formats',
|
||||
'debug',
|
||||
'absolute_urls'
|
||||
],
|
||||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'uuid'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
},
|
||||
formats: {
|
||||
values: models.Post.allowedFormats
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
const models = require('../../models');
|
||||
const common = require('../../lib/common');
|
||||
const urlService = require('../../services/url');
|
||||
const allowedIncludes = ['author', 'tags', 'authors', 'authors.roles'];
|
||||
const unsafeAttrs = ['author_id', 'status', 'authors'];
|
||||
const allowedIncludes = ['tags', 'authors', 'authors.roles'];
|
||||
const unsafeAttrs = ['status', 'authors'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'posts',
|
||||
|
@ -12,7 +12,6 @@ module.exports = {
|
|||
'filter',
|
||||
'fields',
|
||||
'formats',
|
||||
'status',
|
||||
'limit',
|
||||
'order',
|
||||
'page',
|
||||
|
@ -41,7 +40,6 @@ module.exports = {
|
|||
options: [
|
||||
'include',
|
||||
'fields',
|
||||
'status',
|
||||
'formats',
|
||||
'debug',
|
||||
'absolute_urls'
|
||||
|
@ -49,7 +47,6 @@ module.exports = {
|
|||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'status',
|
||||
'uuid'
|
||||
],
|
||||
validation: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const common = require('../../lib/common');
|
||||
const models = require('../../models');
|
||||
const ALLOWED_INCLUDES = ['author', 'authors', 'tags'];
|
||||
const ALLOWED_INCLUDES = ['authors', 'tags'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'preview',
|
||||
|
|
|
@ -14,7 +14,6 @@ module.exports = {
|
|||
'filter',
|
||||
'fields',
|
||||
'limit',
|
||||
'status',
|
||||
'order',
|
||||
'page',
|
||||
'debug'
|
||||
|
@ -42,7 +41,6 @@ module.exports = {
|
|||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'status',
|
||||
'email',
|
||||
'role'
|
||||
],
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
const _ = require('lodash');
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:pages');
|
||||
const converters = require('../../../../../lib/mobiledoc/converters');
|
||||
const url = require('./utils/url');
|
||||
const localUtils = require('../../index');
|
||||
|
||||
function removeMobiledocFormat(frame) {
|
||||
if (frame.options.formats && frame.options.formats.includes('mobiledoc')) {
|
||||
|
@ -40,9 +43,17 @@ module.exports = {
|
|||
frame.options.filter = 'page:true';
|
||||
}
|
||||
|
||||
removeMobiledocFormat(frame);
|
||||
if (localUtils.isContentAPI(frame)) {
|
||||
removeMobiledocFormat(frame);
|
||||
setDefaultOrder(frame);
|
||||
}
|
||||
|
||||
setDefaultOrder(frame);
|
||||
if (!localUtils.isContentAPI(frame)) {
|
||||
// @TODO: remove when we drop v0.1
|
||||
if (!frame.options.filter || !frame.options.filter.match(/status:/)) {
|
||||
frame.options.status = 'all';
|
||||
}
|
||||
}
|
||||
|
||||
debug(frame.options);
|
||||
},
|
||||
|
@ -51,10 +62,52 @@ module.exports = {
|
|||
debug('read');
|
||||
|
||||
frame.data.page = true;
|
||||
removeMobiledocFormat(frame);
|
||||
|
||||
setDefaultOrder(frame);
|
||||
if (localUtils.isContentAPI(frame)) {
|
||||
removeMobiledocFormat(frame);
|
||||
setDefaultOrder(frame);
|
||||
}
|
||||
|
||||
if (!localUtils.isContentAPI(frame)) {
|
||||
// @TODO: remove when we drop v0.1
|
||||
if (!frame.options.filter || !frame.options.filter.match(/status:/)) {
|
||||
frame.data.status = 'all';
|
||||
}
|
||||
}
|
||||
|
||||
debug(frame.options);
|
||||
},
|
||||
|
||||
add(apiConfig, frame) {
|
||||
debug('add');
|
||||
|
||||
if (_.get(frame,'options.source')) {
|
||||
const html = frame.data.pages[0].html;
|
||||
|
||||
if (frame.options.source === 'html' && !_.isEmpty(html)) {
|
||||
frame.data.pages[0].mobiledoc = JSON.stringify(converters.htmlToMobiledocConverter(html));
|
||||
}
|
||||
}
|
||||
|
||||
frame.data.pages[0] = url.forPost(Object.assign({}, frame.data.pages[0]), frame.options);
|
||||
|
||||
// @NOTE: force storing page
|
||||
frame.data.pages[0].page = true;
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
this.add(...arguments);
|
||||
|
||||
debug('edit');
|
||||
|
||||
// @NOTE: force not being able to update a page via pages endpoint
|
||||
frame.options.page = true;
|
||||
},
|
||||
|
||||
destroy(apiConfig, frame) {
|
||||
frame.options.destroyBy = {
|
||||
id: frame.options.id,
|
||||
page: true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const _ = require('lodash');
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:posts');
|
||||
const url = require('./utils/url');
|
||||
const utils = require('../../index');
|
||||
const localUtils = require('../../index');
|
||||
const labs = require('../../../../../services/labs');
|
||||
const converters = require('../../../../../lib/mobiledoc/converters');
|
||||
|
||||
|
@ -38,27 +38,27 @@ module.exports = {
|
|||
browse(apiConfig, frame) {
|
||||
debug('browse');
|
||||
|
||||
/**
|
||||
* CASE:
|
||||
*
|
||||
* - posts endpoint only returns posts, not pages
|
||||
* - we have to enforce the filter
|
||||
*
|
||||
* @TODO: https://github.com/TryGhost/Ghost/issues/10268
|
||||
*/
|
||||
if (frame.options.filter) {
|
||||
frame.options.filter = `(${frame.options.filter})+page:false`;
|
||||
} else {
|
||||
frame.options.filter = 'page:false';
|
||||
}
|
||||
|
||||
/**
|
||||
* ## current cases:
|
||||
* - context object is empty (functional call, content api access)
|
||||
* - api_key.type == 'content' ? content api access
|
||||
* - user exists? admin api access
|
||||
*/
|
||||
if (utils.isContentAPI(frame)) {
|
||||
/**
|
||||
* CASE:
|
||||
*
|
||||
* - the content api endpoints for posts should only return non page type resources
|
||||
* - we have to enforce the filter
|
||||
*
|
||||
* @TODO: https://github.com/TryGhost/Ghost/issues/10268
|
||||
*/
|
||||
if (frame.options.filter) {
|
||||
frame.options.filter = `(${frame.options.filter})+page:false`;
|
||||
} else {
|
||||
frame.options.filter = 'page:false';
|
||||
}
|
||||
|
||||
if (localUtils.isContentAPI(frame)) {
|
||||
// CASE: the content api endpoint for posts should not return mobiledoc
|
||||
removeMobiledocFormat(frame);
|
||||
|
||||
|
@ -70,22 +70,31 @@ module.exports = {
|
|||
setDefaultOrder(frame);
|
||||
}
|
||||
|
||||
if (!localUtils.isContentAPI(frame)) {
|
||||
// @TODO: remove when we drop v0.1
|
||||
if (!frame.options.filter || !frame.options.filter.match(/status:/)) {
|
||||
frame.options.status = 'all';
|
||||
}
|
||||
}
|
||||
|
||||
debug(frame.options);
|
||||
},
|
||||
|
||||
read(apiConfig, frame) {
|
||||
debug('read');
|
||||
|
||||
frame.data.page = false;
|
||||
|
||||
/**
|
||||
* ## current cases:
|
||||
* - context object is empty (functional call, content api access)
|
||||
* - api_key.type == 'content' ? content api access
|
||||
* - user exists? admin api access
|
||||
*/
|
||||
if (utils.isContentAPI(frame)) {
|
||||
frame.data.page = false;
|
||||
if (localUtils.isContentAPI(frame)) {
|
||||
// CASE: the content api endpoint for posts should not return mobiledoc
|
||||
removeMobiledocFormat(frame);
|
||||
|
||||
if (labs.isSet('members')) {
|
||||
// CASE: Members needs to have the tags to check if its allowed access
|
||||
includeTags(frame);
|
||||
|
@ -94,20 +103,18 @@ module.exports = {
|
|||
setDefaultOrder(frame);
|
||||
}
|
||||
|
||||
if (!localUtils.isContentAPI(frame)) {
|
||||
// @TODO: remove when we drop v0.1
|
||||
if (!frame.options.filter || !frame.options.filter.match(/status:/)) {
|
||||
frame.data.status = 'all';
|
||||
}
|
||||
}
|
||||
|
||||
debug(frame.options);
|
||||
},
|
||||
|
||||
add(apiConfig, frame) {
|
||||
debug('add');
|
||||
/**
|
||||
* Convert author property to author_id to match the name in the database.
|
||||
*
|
||||
* @deprecated: `author`, might be removed in Ghost 3.0
|
||||
*/
|
||||
if (frame.data.posts[0].hasOwnProperty('author')) {
|
||||
frame.data.posts[0].author_id = frame.data.posts[0].author;
|
||||
delete frame.data.posts[0].author;
|
||||
}
|
||||
|
||||
if (_.get(frame,'options.source')) {
|
||||
const html = frame.data.posts[0].html;
|
||||
|
@ -118,9 +125,22 @@ module.exports = {
|
|||
}
|
||||
|
||||
frame.data.posts[0] = url.forPost(Object.assign({}, frame.data.posts[0]), frame.options);
|
||||
|
||||
// @NOTE: force storing post
|
||||
frame.data.posts[0].page = false;
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
this.add(apiConfig, frame);
|
||||
|
||||
// @NOTE: force that you cannot update pages via posts endpoint
|
||||
frame.options.page = false;
|
||||
},
|
||||
|
||||
destroy(apiConfig, frame) {
|
||||
frame.options.destroyBy = {
|
||||
id: frame.options.id,
|
||||
page: false
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,6 +5,11 @@ module.exports = {
|
|||
all(models, apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
// CASE: e.g. destroy returns null
|
||||
if (!models) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
pages: models.data.map(model => mapper.mapPost(model, frame)),
|
||||
|
|
|
@ -98,6 +98,8 @@ const post = (attrs, frame) => {
|
|||
if (attrs.og_description === '') {
|
||||
attrs.og_description = null;
|
||||
}
|
||||
} else {
|
||||
delete attrs.page;
|
||||
}
|
||||
|
||||
delete attrs.locale;
|
||||
|
|
|
@ -3,6 +3,10 @@ module.exports = {
|
|||
return require('./posts');
|
||||
},
|
||||
|
||||
get pages() {
|
||||
return require('./pages');
|
||||
},
|
||||
|
||||
get invites() {
|
||||
return require('./invites');
|
||||
},
|
||||
|
|
37
core/server/api/v2/utils/validators/input/pages.js
Normal file
37
core/server/api/v2/utils/validators/input/pages.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const Promise = require('bluebird');
|
||||
const common = require('../../../../../lib/common');
|
||||
const utils = require('../../index');
|
||||
const jsonSchema = require('../utils/json-schema');
|
||||
|
||||
module.exports = {
|
||||
add(apiConfig, frame) {
|
||||
/**
|
||||
* @NOTE:
|
||||
*
|
||||
* Session authentication does not require authors, because the logged in user
|
||||
* becomes the primary author.
|
||||
*
|
||||
* Admin API key requires sending authors, because there is no user id.
|
||||
*/
|
||||
if (utils.isAdminAPIKey(frame)) {
|
||||
if (!frame.data.pages[0].hasOwnProperty('authors')) {
|
||||
return Promise.reject(new common.errors.ValidationError({
|
||||
message: common.i18n.t('notices.data.validation.index.validationFailed', {
|
||||
validationName: 'FieldIsRequired',
|
||||
key: '"authors"'
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const schema = require(`./schemas/pages-add`);
|
||||
const definitions = require('./schemas/pages');
|
||||
return jsonSchema.validate(schema, definitions, frame.data);
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
const schema = require(`./schemas/pages-edit`);
|
||||
const definitions = require('./schemas/pages');
|
||||
return jsonSchema.validate(schema, definitions, frame.data);
|
||||
}
|
||||
};
|
|
@ -24,24 +24,6 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure correct incoming `post.authors` structure.
|
||||
*
|
||||
* NOTE:
|
||||
* The `post.authors[*].id` attribute is required till we release Ghost 3.0.
|
||||
* Ghost 1.x keeps the deprecated support for `post.author_id`, which is the primary author id and needs to be
|
||||
* updated if the order of the `post.authors` array changes.
|
||||
* If we allow adding authors via the post endpoint e.g. `authors=[{name: 'newuser']` (no id property), it's hard
|
||||
* to update the primary author id (`post.author_id`), because the new author `id` is generated when attaching
|
||||
* the author to the post. And the attach operation happens in bookshelf-relations, which happens after
|
||||
* the event handling in the post model.
|
||||
*
|
||||
* It's solvable, but not worth right now solving, because the admin UI does not support this feature.
|
||||
*
|
||||
* TLDR; You can only attach existing authors to a post.
|
||||
*
|
||||
* @TODO: remove `id` restriction in Ghost 3.0
|
||||
*/
|
||||
const schema = require(`./schemas/posts-add`);
|
||||
const definitions = require('./schemas/posts');
|
||||
return jsonSchema.validate(schema, definitions, frame.data);
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "pages.add",
|
||||
"title": "pages.add",
|
||||
"description": "Schema for pages.add",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"allOf": [{"$ref": "pages#/definitions/page"}],
|
||||
"required": ["title"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["pages"]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "pages.edit",
|
||||
"title": "pages.edit",
|
||||
"description": "Schema for pages.edit",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"allOf": [{"$ref": "pages#/definitions/page"}],
|
||||
"required": ["updated_at"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["pages"]
|
||||
}
|
206
core/server/api/v2/utils/validators/input/schemas/pages.json
Normal file
206
core/server/api/v2/utils/validators/input/schemas/pages.json
Normal file
|
@ -0,0 +1,206 @@
|
|||
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "pages",
|
||||
"title": "pages",
|
||||
"description": "Base pages definitions",
|
||||
"definitions": {
|
||||
"page": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 2000
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"maxLength": 191
|
||||
},
|
||||
"mobiledoc": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 1000000000
|
||||
},
|
||||
"html": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 1000000000
|
||||
},
|
||||
"feature_image": {
|
||||
"type": ["string", "null"],
|
||||
"format": "uri-reference",
|
||||
"maxLength": 2000
|
||||
},
|
||||
"featured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["published", "draft", "scheduled"]
|
||||
},
|
||||
"locale": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 6
|
||||
},
|
||||
"visibility": {
|
||||
"type": ["string", "null"],
|
||||
"enum": ["public"]
|
||||
},
|
||||
"meta_title": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 300
|
||||
},
|
||||
"meta_description": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 500
|
||||
},
|
||||
"updated_at": {
|
||||
"type": ["string", "null"],
|
||||
"format": "date-time"
|
||||
},
|
||||
"published_at": {
|
||||
"type": ["string", "null"],
|
||||
"format": "date-time"
|
||||
},
|
||||
"custom_excerpt": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 300
|
||||
},
|
||||
"codeinjection_head": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 65535
|
||||
},
|
||||
"codeinjection_foot": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 65535
|
||||
},
|
||||
"og_image": {
|
||||
"type": ["string", "null"],
|
||||
"format": "uri-reference",
|
||||
"maxLength": 2000
|
||||
},
|
||||
"og_title": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 300
|
||||
},
|
||||
"og_description": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 500
|
||||
},
|
||||
"twitter_image": {
|
||||
"type": ["string", "null"],
|
||||
"format": "uri-reference",
|
||||
"maxLength": 2000
|
||||
},
|
||||
"twitter_title": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 300
|
||||
},
|
||||
"twitter_description": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 500
|
||||
},
|
||||
"custom_template": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 100
|
||||
},
|
||||
"authors": {
|
||||
"$ref": "#/definitions/page-authors"
|
||||
},
|
||||
"tags": {
|
||||
"$ref": "#/definitions/page-tags"
|
||||
},
|
||||
"id": {
|
||||
"strip": true
|
||||
},
|
||||
"page": {
|
||||
"strip": true
|
||||
},
|
||||
"author": {
|
||||
"strip": true
|
||||
},
|
||||
"author_id": {
|
||||
"strip": true
|
||||
},
|
||||
"created_at": {
|
||||
"strip": true
|
||||
},
|
||||
"created_by": {
|
||||
"strip": true
|
||||
},
|
||||
"updated_by": {
|
||||
"strip": true
|
||||
},
|
||||
"published_by": {
|
||||
"strip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"page-authors": {
|
||||
"description": "Authors of the page",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"maxLength": 24
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"maxLength": 191
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"maxLength": 191
|
||||
},
|
||||
"roles": {
|
||||
"strip": true
|
||||
},
|
||||
"permissions": {
|
||||
"strip": true
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{"required": ["id"]},
|
||||
{"required": ["slug"]},
|
||||
{"required": ["email"]}
|
||||
]
|
||||
}
|
||||
},
|
||||
"page-tags": {
|
||||
"description": "Tags of the page",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"maxLength": 24
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 191
|
||||
},
|
||||
"slug": {
|
||||
"type": ["string", "null"],
|
||||
"maxLength": 191
|
||||
},
|
||||
"parent": {
|
||||
"strip": true
|
||||
},
|
||||
"parent_id": {
|
||||
"strip": true
|
||||
},
|
||||
"pages": {
|
||||
"strip": true
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{"required": ["id"]},
|
||||
{"required": ["name"]},
|
||||
{"required": ["slug"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,9 +33,6 @@
|
|||
"featured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"page": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["published", "draft", "scheduled"]
|
||||
|
@ -115,6 +112,15 @@
|
|||
"id": {
|
||||
"strip": true
|
||||
},
|
||||
"author": {
|
||||
"strip": true
|
||||
},
|
||||
"author_id": {
|
||||
"strip": true
|
||||
},
|
||||
"page": {
|
||||
"strip": true
|
||||
},
|
||||
"created_at": {
|
||||
"strip": true
|
||||
},
|
||||
|
|
|
@ -17,13 +17,13 @@ var proxy = require('./proxy'),
|
|||
|
||||
/**
|
||||
* v0.1: users, posts, tags
|
||||
* v2: authors, pages, posts, tagsPublic
|
||||
* v2: authors, pagesPublic, posts, tagsPublic
|
||||
*
|
||||
* @NOTE: if you use "users" in v2, we should fallback to authors
|
||||
*/
|
||||
const RESOURCES = {
|
||||
posts: {
|
||||
alias: 'posts',
|
||||
alias: 'postsPublic',
|
||||
resource: 'posts'
|
||||
},
|
||||
tags: {
|
||||
|
@ -35,7 +35,7 @@ const RESOURCES = {
|
|||
resource: 'users'
|
||||
},
|
||||
pages: {
|
||||
alias: 'pages',
|
||||
alias: 'pagesPublic',
|
||||
resource: 'posts'
|
||||
},
|
||||
authors: {
|
||||
|
|
|
@ -936,7 +936,14 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||
edit: function edit(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'edit');
|
||||
const id = options.id;
|
||||
const model = this.forge({id: id});
|
||||
let model;
|
||||
|
||||
if (options.hasOwnProperty('page')) {
|
||||
model = this.forge({id: id, page: options.page});
|
||||
delete options.page;
|
||||
} else {
|
||||
model = this.forge({id: id});
|
||||
}
|
||||
|
||||
data = this.filterData(data);
|
||||
|
||||
|
@ -945,12 +952,16 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||
model.hasTimestamps = false;
|
||||
}
|
||||
|
||||
return model.fetch(options).then(function then(object) {
|
||||
if (object) {
|
||||
options.method = 'update';
|
||||
return object.save(data, options);
|
||||
}
|
||||
});
|
||||
return model
|
||||
.fetch(options)
|
||||
.then((object) => {
|
||||
if (object) {
|
||||
options.method = 'update';
|
||||
return object.save(data, options);
|
||||
}
|
||||
|
||||
throw new common.errors.NotFoundError();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -987,6 +998,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||
*/
|
||||
destroy: function destroy(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'destroy');
|
||||
|
||||
if (!options.destroyBy) {
|
||||
options.destroyBy = {
|
||||
id: options.id
|
||||
|
|
|
@ -704,7 +704,8 @@ Post = ghostBookshelf.Model.extend({
|
|||
findOne: ['columns', 'importing', 'withRelated', 'require'],
|
||||
findPage: ['status', 'staticPages'],
|
||||
findAll: ['columns', 'filter'],
|
||||
destroy: ['destroyAll']
|
||||
destroy: ['destroyAll', 'destroyBy'],
|
||||
edit: ['page']
|
||||
};
|
||||
|
||||
// The post model additionally supports having a formats option
|
||||
|
|
|
@ -18,7 +18,7 @@ module.exports.QUERY = {
|
|||
}
|
||||
},
|
||||
post: {
|
||||
controller: 'posts',
|
||||
controller: 'postsPublic',
|
||||
type: 'read',
|
||||
resource: 'posts',
|
||||
options: {
|
||||
|
@ -26,7 +26,7 @@ module.exports.QUERY = {
|
|||
}
|
||||
},
|
||||
page: {
|
||||
controller: 'pages',
|
||||
controller: 'pagesPublic',
|
||||
type: 'read',
|
||||
resource: 'pages',
|
||||
options: {
|
||||
|
|
|
@ -12,6 +12,7 @@ const notImplemented = function (req, res, next) {
|
|||
const whitelisted = {
|
||||
// @NOTE: stable
|
||||
posts: ['GET', 'PUT', 'DELETE', 'POST'],
|
||||
pages: ['GET', 'PUT', 'DELETE', 'POST'],
|
||||
tags: ['GET', 'PUT', 'DELETE', 'POST'],
|
||||
images: ['POST'],
|
||||
// @NOTE: experimental
|
||||
|
|
|
@ -32,6 +32,14 @@ module.exports = function apiRoutes() {
|
|||
router.put('/posts/:id', mw.authAdminApi, http(apiv2.posts.edit));
|
||||
router.del('/posts/:id', mw.authAdminApi, http(apiv2.posts.destroy));
|
||||
|
||||
// ## Pages
|
||||
router.get('/pages', mw.authAdminApi, http(apiv2.pages.browse));
|
||||
router.post('/pages', mw.authAdminApi, http(apiv2.pages.add));
|
||||
router.get('/pages/:id', mw.authAdminApi, http(apiv2.pages.read));
|
||||
router.get('/pages/slug/:slug', mw.authAdminApi, http(apiv2.pages.read));
|
||||
router.put('/pages/:id', mw.authAdminApi, http(apiv2.pages.edit));
|
||||
router.del('/pages/:id', mw.authAdminApi, http(apiv2.pages.destroy));
|
||||
|
||||
// # Integrations
|
||||
|
||||
router.get('/integrations', mw.authAdminApi, http(apiv2.integrations.browse));
|
||||
|
|
|
@ -11,14 +11,14 @@ module.exports = function apiRoutes() {
|
|||
const http = apiImpl => apiv2.http(apiImpl, 'content');
|
||||
|
||||
// ## Posts
|
||||
router.get('/posts', mw.authenticatePublic, http(apiv2.posts.browse));
|
||||
router.get('/posts/:id', mw.authenticatePublic, http(apiv2.posts.read));
|
||||
router.get('/posts/slug/:slug', mw.authenticatePublic, http(apiv2.posts.read));
|
||||
router.get('/posts', mw.authenticatePublic, http(apiv2.postsPublic.browse));
|
||||
router.get('/posts/:id', mw.authenticatePublic, http(apiv2.postsPublic.read));
|
||||
router.get('/posts/slug/:slug', mw.authenticatePublic, http(apiv2.postsPublic.read));
|
||||
|
||||
// ## Pages
|
||||
router.get('/pages', mw.authenticatePublic, http(apiv2.pages.browse));
|
||||
router.get('/pages/:id', mw.authenticatePublic, http(apiv2.pages.read));
|
||||
router.get('/pages/slug/:slug', mw.authenticatePublic, http(apiv2.pages.read));
|
||||
router.get('/pages', mw.authenticatePublic, http(apiv2.pagesPublic.browse));
|
||||
router.get('/pages/:id', mw.authenticatePublic, http(apiv2.pagesPublic.read));
|
||||
router.get('/pages/slug/:slug', mw.authenticatePublic, http(apiv2.pagesPublic.read));
|
||||
|
||||
// ## Users
|
||||
router.get('/authors', mw.authenticatePublic, http(apiv2.authors.browse));
|
||||
|
|
|
@ -102,7 +102,6 @@ describe('DB API', () => {
|
|||
let jsonResponse = res.body;
|
||||
let results = jsonResponse.posts;
|
||||
jsonResponse.posts.should.have.length(7);
|
||||
_.filter(results, {page: false, status: 'published'}).length.should.equal(7);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -118,7 +117,6 @@ describe('DB API', () => {
|
|||
let jsonResponse = res.body;
|
||||
let results = jsonResponse.posts;
|
||||
jsonResponse.posts.should.have.length(7);
|
||||
_.filter(results, {page: false, status: 'published'}).length.should.equal(7);
|
||||
})
|
||||
.then(() => {
|
||||
return request.delete(localUtils.API.getApiQuery('db/'))
|
||||
|
|
154
core/test/acceptance/old/admin/pages_spec.js
Normal file
154
core/test/acceptance/old/admin/pages_spec.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
const should = require('should');
|
||||
const supertest = require('supertest');
|
||||
const _ = require('lodash');
|
||||
const ObjectId = require('bson-objectid');
|
||||
const moment = require('moment-timezone');
|
||||
const testUtils = require('../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../server/config');
|
||||
const models = require('../../../../server/models');
|
||||
const ghost = testUtils.startGhost;
|
||||
let request;
|
||||
|
||||
describe('Pages API', function () {
|
||||
let ghostServer;
|
||||
let ownerCookie;
|
||||
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
ghostServer = _ghostServer;
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'users:extra', 'posts');
|
||||
})
|
||||
.then(function (cookie) {
|
||||
ownerCookie = cookie;
|
||||
});
|
||||
});
|
||||
|
||||
it('Can retrieve all pages', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('pages/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.pages);
|
||||
localUtils.API.checkResponse(jsonResponse, 'pages');
|
||||
jsonResponse.pages.should.have.length(2);
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.pages[0], 'page');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
_.isBoolean(jsonResponse.pages[0].featured).should.eql(true);
|
||||
|
||||
// Absolute urls by default
|
||||
jsonResponse.pages[0].url.should.eql(`${config.get('url')}/404/`);
|
||||
jsonResponse.pages[1].url.should.eql(`${config.get('url')}/static-page-test/`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Can add a page', function () {
|
||||
const page = {
|
||||
title: 'My Page',
|
||||
page: false,
|
||||
status: 'published'
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('pages/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({pages: [page]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(201)
|
||||
.then((res) => {
|
||||
res.body.pages.length.should.eql(1);
|
||||
localUtils.API.checkResponse(res.body.pages[0], 'page');
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
return models.Post.findOne({
|
||||
id: res.body.pages[0].id
|
||||
}, testUtils.context.internal);
|
||||
})
|
||||
.then((model) => {
|
||||
model.get('title').should.eql(page.title);
|
||||
model.get('status').should.eql(page.status);
|
||||
model.get('page').should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can update a page', function () {
|
||||
const page = {
|
||||
title: 'updated page',
|
||||
page: false
|
||||
};
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[5].id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
page.updated_at = res.body.pages[0].updated_at;
|
||||
|
||||
return request.put(localUtils.API.getApiQuery('pages/' + testUtils.DataGenerator.Content.posts[5].id))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({pages: [page]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200);
|
||||
})
|
||||
.then((res) => {
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
localUtils.API.checkResponse(res.body.pages[0], 'page');
|
||||
|
||||
return models.Post.findOne({
|
||||
id: res.body.pages[0].id
|
||||
}, testUtils.context.internal);
|
||||
})
|
||||
.then((model) => {
|
||||
model.get('page').should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot get page via posts endpoint', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[5].id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Cannot update page via posts endpoint', function () {
|
||||
const page = {
|
||||
title: 'fails',
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return request.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[5].id))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [page]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Can delete a page', function () {
|
||||
return request.del(localUtils.API.getApiQuery('pages/' + testUtils.DataGenerator.Content.posts[5].id))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
res.body.should.be.empty();
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -43,19 +43,19 @@ describe('Posts API', function () {
|
|||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
jsonResponse.posts.should.have.length(13);
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
// Ensure default order
|
||||
jsonResponse.posts[0].slug.should.eql('welcome');
|
||||
jsonResponse.posts[10].slug.should.eql('html-ipsum');
|
||||
jsonResponse.posts[0].slug.should.eql('scheduled-post');
|
||||
jsonResponse.posts[12].slug.should.eql('html-ipsum');
|
||||
|
||||
// Absolute urls by default
|
||||
jsonResponse.posts[0].url.should.eql(`${config.get('url')}/welcome/`);
|
||||
jsonResponse.posts[9].feature_image.should.eql(`${config.get('url')}/content/images/2018/hey.jpg`);
|
||||
jsonResponse.posts[0].url.should.eql(`${config.get('url')}/404/`);
|
||||
jsonResponse.posts[2].url.should.eql(`${config.get('url')}/welcome/`);
|
||||
jsonResponse.posts[11].feature_image.should.eql(`${config.get('url')}/content/images/2018/hey.jpg`);
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -80,7 +80,6 @@ describe('Posts API', function () {
|
|||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['mobiledoc', 'plaintext'], ['html']);
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
// ensure order works
|
||||
jsonResponse.posts[0].slug.should.eql('apps-integrations');
|
||||
|
@ -104,7 +103,7 @@ describe('Posts API', function () {
|
|||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
jsonResponse.posts.should.have.length(13);
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post',
|
||||
|
@ -113,17 +112,18 @@ describe('Posts API', function () {
|
|||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
jsonResponse.posts[0].tags.length.should.eql(1);
|
||||
jsonResponse.posts[0].authors.length.should.eql(1);
|
||||
jsonResponse.posts[0].tags[0].url.should.eql(`${config.get('url')}/tag/getting-started/`);
|
||||
jsonResponse.posts[0].authors[0].url.should.eql(`${config.get('url')}/author/ghost/`);
|
||||
jsonResponse.posts[0].tags.length.should.eql(0);
|
||||
jsonResponse.posts[2].tags.length.should.eql(1);
|
||||
jsonResponse.posts[2].authors.length.should.eql(1);
|
||||
jsonResponse.posts[2].tags[0].url.should.eql(`${config.get('url')}/tag/getting-started/`);
|
||||
jsonResponse.posts[2].authors[0].url.should.eql(`${config.get('url')}/author/ghost/`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Can filter posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?filter=page:[false,true]&status=all'))
|
||||
request.get(localUtils.API.getApiQuery('posts/?filter=featured:true'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -137,13 +137,33 @@ describe('Posts API', function () {
|
|||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(15);
|
||||
jsonResponse.posts.should.have.length(2);
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot receive pages', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?filter=page:true'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Can paginate posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?page=2'))
|
||||
.set('Origin', config.get('url'))
|
||||
|
@ -178,9 +198,9 @@ describe('Posts API', function () {
|
|||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
jsonResponse.posts[0].id.should.equal(testUtils.DataGenerator.Content.posts[0].id);
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
testUtils.API.isISO8601(jsonResponse.posts[0].created_at).should.be.true();
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
|
@ -205,9 +225,9 @@ describe('Posts API', function () {
|
|||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
jsonResponse.posts[0].slug.should.equal('welcome');
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
done();
|
||||
|
@ -233,8 +253,6 @@ describe('Posts API', function () {
|
|||
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['tags', 'authors']);
|
||||
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
|
||||
jsonResponse.posts[0].authors[0].should.be.an.Object();
|
||||
localUtils.API.checkResponse(jsonResponse.posts[0].authors[0], 'user', ['url']);
|
||||
|
||||
|
@ -289,7 +307,7 @@ describe('Posts API', function () {
|
|||
};
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[3].id}/?status=all`))
|
||||
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[3].id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
|
@ -313,7 +331,7 @@ describe('Posts API', function () {
|
|||
};
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[1].id}/?status=all`))
|
||||
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[1].id}/?`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
|
@ -344,4 +362,26 @@ describe('Posts API', function () {
|
|||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot get post via pages endpoint', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[3].id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Cannot update post via pages endpoint', function () {
|
||||
const post = {
|
||||
title: 'fails',
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return request.put(localUtils.API.getApiQuery('pages/' + testUtils.DataGenerator.Content.posts[3].id))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({pages: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ const API_URL = '/ghost/api/v2/admin/';
|
|||
const expectedProperties = {
|
||||
// API top level
|
||||
posts: ['posts', 'meta'],
|
||||
pages: ['pages', 'meta'],
|
||||
tags: ['tags', 'meta'],
|
||||
users: ['users', 'meta'],
|
||||
settings: ['settings', 'meta'],
|
||||
|
@ -27,9 +28,22 @@ const expectedProperties = {
|
|||
.without('mobiledoc', 'plaintext')
|
||||
.without('visibility')
|
||||
.without('locale')
|
||||
.without('page')
|
||||
// always returns computed properties: url, comment_id, primary_tag, primary_author
|
||||
.without('author_id').concat('url', 'primary_tag', 'primary_author')
|
||||
,
|
||||
|
||||
page: _(schema.posts)
|
||||
.keys()
|
||||
// by default we only return html
|
||||
.without('mobiledoc', 'plaintext')
|
||||
.without('visibility')
|
||||
.without('locale')
|
||||
.without('page')
|
||||
// always returns computed properties: url, comment_id, primary_tag, primary_author
|
||||
.without('author_id').concat('url', 'primary_tag', 'primary_author')
|
||||
,
|
||||
|
||||
user: _(schema.users)
|
||||
.keys()
|
||||
.without('visibility')
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Posts API', function () {
|
|||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
jsonResponse.posts.should.have.length(13);
|
||||
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
|
@ -74,7 +74,8 @@ describe('Posts API', function () {
|
|||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
jsonResponse.posts.should.have.length(13);
|
||||
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post',
|
||||
|
|
|
@ -24,6 +24,7 @@ const expectedProperties = {
|
|||
.without('mobiledoc', 'plaintext')
|
||||
.without('visibility')
|
||||
.without('locale')
|
||||
.without('page')
|
||||
// always returns computed properties: url, comment_id, primary_tag, primary_author
|
||||
.without('author_id').concat('url', 'primary_tag', 'primary_author')
|
||||
,
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
};
|
||||
|
||||
serializers.input.posts.browse(apiConfig, frame);
|
||||
should.equal(frame.options.filter, undefined);
|
||||
should.equal(frame.options.filter, 'page:false');
|
||||
});
|
||||
|
||||
it('combine filters', function () {
|
||||
|
@ -178,28 +178,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
};
|
||||
|
||||
serializers.input.posts.read(apiConfig, frame);
|
||||
should.not.exist(frame.data.page);
|
||||
});
|
||||
|
||||
it('with non public request it does not override data.page', function () {
|
||||
const apiConfig = {};
|
||||
const frame = {
|
||||
apiType: 'admin',
|
||||
options: {
|
||||
context: {
|
||||
api_key: {
|
||||
id: 1,
|
||||
type: 'admin'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
page: true
|
||||
}
|
||||
};
|
||||
|
||||
serializers.input.posts.read(apiConfig, frame);
|
||||
frame.data.page.should.eql(true);
|
||||
should.equal(frame.data.page, false);
|
||||
});
|
||||
|
||||
it('remove mobiledoc option from formats', function () {
|
||||
|
@ -382,6 +361,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
const apiConfig = {};
|
||||
const mobiledoc = '{"version":"0.3.1","atoms":[],"cards":[],"sections":[]}';
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
|
|
393
core/test/unit/api/v2/utils/validators/input/pages_spec.js
Normal file
393
core/test/unit/api/v2/utils/validators/input/pages_spec.js
Normal file
|
@ -0,0 +1,393 @@
|
|||
const _ = require('lodash');
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const Promise = require('bluebird');
|
||||
const common = require('../../../../../../../server/lib/common');
|
||||
const validators = require('../../../../../../../server/api/v2/utils/validators');
|
||||
|
||||
describe('Unit: v2/utils/validators/input/pages', function () {
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
const apiConfig = {
|
||||
docName: 'pages'
|
||||
};
|
||||
|
||||
describe('required fields', function () {
|
||||
it('should fail with no data', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with no pages', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
tags: []
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with no pages in array', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: []
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with more than page', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [],
|
||||
tags: []
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail without required fields', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [{
|
||||
what: 'a fail'
|
||||
}],
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass with required fields', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [{
|
||||
title: 'pass',
|
||||
authors: [{id: 'correct'}]
|
||||
}],
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame);
|
||||
});
|
||||
|
||||
it('should remove `strip`able fields and leave regular fields', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [{
|
||||
title: 'pass',
|
||||
authors: [{id: 'correct'}],
|
||||
id: 'strip me',
|
||||
created_at: 'strip me',
|
||||
created_by: 'strip me',
|
||||
updated_by: 'strip me',
|
||||
published_by: 'strip me'
|
||||
}],
|
||||
}
|
||||
};
|
||||
|
||||
let result = validators.input.pages.add(apiConfig, frame);
|
||||
|
||||
should.exist(frame.data.pages[0].title);
|
||||
should.exist(frame.data.pages[0].authors);
|
||||
should.not.exist(frame.data.pages[0].id);
|
||||
should.not.exist(frame.data.pages[0].created_at);
|
||||
should.not.exist(frame.data.pages[0].created_by);
|
||||
should.not.exist(frame.data.pages[0].updated_by);
|
||||
should.not.exist(frame.data.pages[0].published_by);
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
describe('field formats', function () {
|
||||
const fieldMap = {
|
||||
title: [123, new Date(), _.repeat('a', 2001)],
|
||||
slug: [123, new Date(), _.repeat('a', 192)],
|
||||
mobiledoc: [123, new Date()],
|
||||
feature_image: [123, new Date(), 'random words'],
|
||||
featured: [123, new Date(), 'abc'],
|
||||
status: [123, new Date(), 'abc'],
|
||||
locale: [123, new Date(), _.repeat('a', 7)],
|
||||
visibility: [123, new Date(), 'abc'],
|
||||
meta_title: [123, new Date(), _.repeat('a', 301)],
|
||||
meta_description: [123, new Date(), _.repeat('a', 501)],
|
||||
};
|
||||
|
||||
Object.keys(fieldMap).forEach(key => {
|
||||
it(`should fail for bad ${key}`, function () {
|
||||
const badValues = fieldMap[key];
|
||||
|
||||
const checks = badValues.map((value) => {
|
||||
const page = {};
|
||||
page[key] = value;
|
||||
|
||||
if (key !== 'title') {
|
||||
page.title = 'abc';
|
||||
}
|
||||
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [page]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(checks);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('authors structure', function () {
|
||||
it('should require properties', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
authors: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require id', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
authors: [{
|
||||
name: 'hey'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
authors: [{
|
||||
id: 'correct',
|
||||
name: 'ja'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.add(apiConfig, frame);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
const apiConfig = {
|
||||
docName: 'pages'
|
||||
};
|
||||
|
||||
describe('required fields', function () {
|
||||
it('should fail with no data', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with no pages', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
tags: []
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with more than page', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [],
|
||||
tags: []
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass with some fields', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [{
|
||||
title: 'pass',
|
||||
updated_at: new Date().toISOString()
|
||||
}],
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authors structure', function () {
|
||||
it('should require properties', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
authors: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require id', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
authors: [{
|
||||
name: 'hey'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.ValidationError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass with valid authors', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
updated_at: new Date().toISOString(),
|
||||
authors: [{
|
||||
id: 'correct',
|
||||
name: 'ja'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame);
|
||||
});
|
||||
|
||||
it('should pass without authors', function () {
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
title: 'cool',
|
||||
updated_at: new Date().toISOString()
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.pages.edit(apiConfig, frame);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -143,7 +143,6 @@ describe('Unit: v2/utils/validators/input/posts', function () {
|
|||
mobiledoc: [123, new Date()],
|
||||
feature_image: [123, new Date(), 'random words'],
|
||||
featured: [123, new Date(), 'abc'],
|
||||
page: [123, new Date(), 'abc'],
|
||||
status: [123, new Date(), 'abc'],
|
||||
locale: [123, new Date(), _.repeat('a', 7)],
|
||||
visibility: [123, new Date(), 'abc'],
|
||||
|
|
|
@ -4,6 +4,7 @@ var should = require('should'),
|
|||
Promise = require('bluebird'),
|
||||
security = require('../../../../server/lib/security'),
|
||||
models = require('../../../../server/models'),
|
||||
common = require('../../../../server/lib/common'),
|
||||
urlService = require('../../../../server/services/url'),
|
||||
filters = require('../../../../server/filters'),
|
||||
testUtils = require('../../../utils');
|
||||
|
@ -338,7 +339,7 @@ describe('Models: base', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('resolves with nothing and does not call save if no model is fetched', function () {
|
||||
it('throws an error if model cannot be found on edit', function () {
|
||||
const data = {
|
||||
db: 'cooper'
|
||||
};
|
||||
|
@ -354,9 +355,10 @@ describe('Models: base', function () {
|
|||
.resolves();
|
||||
const saveSpy = sinon.stub(model, 'save');
|
||||
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then((result) => {
|
||||
should.equal(result, undefined);
|
||||
should.equal(saveSpy.callCount, 0);
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then(() => {
|
||||
throw new Error('That should not happen');
|
||||
}).catch((err) => {
|
||||
(err instanceof common.errors.NotFoundError).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -273,7 +273,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () {
|
|||
describe('static pages', function () {
|
||||
const routerOptions = {
|
||||
permalinks: '/:slug/',
|
||||
query: {controller: 'pages', resource: 'pages'}
|
||||
query: {controller: 'pagesPublic', resource: 'pages'}
|
||||
};
|
||||
|
||||
let pages;
|
||||
|
@ -299,7 +299,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () {
|
|||
};
|
||||
});
|
||||
|
||||
sinon.stub(api.v2, 'pages').get(() => {
|
||||
sinon.stub(api.v2, 'pagesPublic').get(() => {
|
||||
return {
|
||||
read: pagesReadStub
|
||||
};
|
||||
|
@ -350,7 +350,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () {
|
|||
};
|
||||
});
|
||||
|
||||
sinon.stub(api.v2, 'pages').get(() => {
|
||||
sinon.stub(api.v2, 'pagesPublic').get(() => {
|
||||
return {
|
||||
read: pagesReadStub
|
||||
};
|
||||
|
|
|
@ -1288,7 +1288,7 @@ describe('UNIT: services/settings/validate', function () {
|
|||
data: {
|
||||
query: {
|
||||
home: {
|
||||
controller: 'pages',
|
||||
controller: 'pagesPublic',
|
||||
resource: 'pages',
|
||||
type: 'read',
|
||||
options: {
|
||||
|
@ -1399,7 +1399,7 @@ describe('UNIT: services/settings/validate', function () {
|
|||
data: {
|
||||
query: {
|
||||
food: {
|
||||
controller: 'posts',
|
||||
controller: 'postsPublic',
|
||||
resource: 'posts',
|
||||
type: 'browse',
|
||||
options: {}
|
||||
|
@ -1415,7 +1415,7 @@ describe('UNIT: services/settings/validate', function () {
|
|||
data: {
|
||||
query: {
|
||||
posts: {
|
||||
controller: 'posts',
|
||||
controller: 'postsPublic',
|
||||
resource: 'posts',
|
||||
type: 'read',
|
||||
options: {
|
||||
|
@ -1454,7 +1454,7 @@ describe('UNIT: services/settings/validate', function () {
|
|||
data: {
|
||||
query: {
|
||||
gym: {
|
||||
controller: 'posts',
|
||||
controller: 'postsPublic',
|
||||
resource: 'posts',
|
||||
type: 'read',
|
||||
options: {
|
||||
|
|
Loading…
Add table
Reference in a new issue