mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 19:33:02 +03:00
✨ Added ?search=
param to Admin API members endpoint (#11854)
no issue - adds `search` bookshelf plugin that calls out to an optional `searchQuery()` method on individual models to apply model-specific SQL conditions to queries - updated the base model's `findPage()` method to use the search plugin within `findPage` calls - added a `searchQuery` method to the `member` model that performs a basic `LIKE %query%` for both `name` and `email` columns - allowed the `?search=` parameter to pass through in the `options` object for member browse requests
This commit is contained in:
parent
e7dc5f0bb3
commit
35f8042d7b
@ -104,7 +104,8 @@ const members = {
|
||||
'filter',
|
||||
'order',
|
||||
'debug',
|
||||
'page'
|
||||
'page',
|
||||
'search'
|
||||
],
|
||||
permissions: true,
|
||||
validation: {},
|
||||
|
@ -38,6 +38,9 @@ ghostBookshelf.plugin(plugins.transactionEvents);
|
||||
// Load the Ghost filter plugin, which handles applying a 'filter' to findPage requests
|
||||
ghostBookshelf.plugin(plugins.filter);
|
||||
|
||||
// Load the Ghost search plugin, which handles applying a search query to findPage requests
|
||||
ghostBookshelf.plugin(plugins.search);
|
||||
|
||||
// Load the Ghost include count plugin, which allows for the inclusion of cross-table counts
|
||||
ghostBookshelf.plugin(plugins.includeCount);
|
||||
|
||||
@ -885,6 +888,9 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||
// Add Filter behaviour
|
||||
itemCollection.applyDefaultAndCustomFilters(options);
|
||||
|
||||
// Apply model-specific search behaviour
|
||||
itemCollection.applySearchQuery(options);
|
||||
|
||||
// Ensure only valid fields/columns are added to query
|
||||
// and append default columns to fetch
|
||||
if (options.columns) {
|
||||
|
@ -154,6 +154,11 @@ const Member = ghostBookshelf.Model.extend({
|
||||
return options;
|
||||
},
|
||||
|
||||
searchQuery: function searchQuery(queryBuilder, query) {
|
||||
queryBuilder.where('name', 'like', `%${query}%`);
|
||||
queryBuilder.orWhere('email', 'like', `%${query}%`);
|
||||
},
|
||||
|
||||
toJSON(unfilteredOptions) {
|
||||
const options = Member.filterOptions(unfilteredOptions, 'toJSON');
|
||||
const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
|
||||
@ -169,6 +174,21 @@ const Member = ghostBookshelf.Model.extend({
|
||||
|
||||
return attrs;
|
||||
}
|
||||
}, {
|
||||
/**
|
||||
* Returns an array of keys permitted in a method's `options` hash, depending on the current method.
|
||||
* @param {String} methodName The name of the method to check valid options for.
|
||||
* @return {Array} Keys allowed in the `options` hash of the model's method.
|
||||
*/
|
||||
permittedOptions: function permittedOptions(methodName) {
|
||||
let options = ghostBookshelf.Model.permittedOptions.call(this, methodName);
|
||||
|
||||
if (['findPage', 'findAll'].includes(methodName)) {
|
||||
options = options.concat(['search']);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
||||
const Members = ghostBookshelf.Collection.extend({
|
||||
|
@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
filter: require('./filter'),
|
||||
search: require('./search'),
|
||||
includeCount: require('./include-count'),
|
||||
pagination: require('./pagination'),
|
||||
collision: require('./collision'),
|
||||
|
18
core/server/models/plugins/search.js
Normal file
18
core/server/models/plugins/search.js
Normal file
@ -0,0 +1,18 @@
|
||||
const searchPlugin = function searchPlugin(Bookshelf) {
|
||||
const Model = Bookshelf.Model.extend({
|
||||
// override this on the model itself
|
||||
searchQuery() {},
|
||||
|
||||
applySearchQuery: function applySearchQuery(options) {
|
||||
if (options.search) {
|
||||
this.query((qb) => {
|
||||
this.searchQuery(qb, options.search);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Bookshelf.Model = Model;
|
||||
};
|
||||
|
||||
module.exports = searchPlugin;
|
@ -76,6 +76,26 @@ describe('Members API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Can browse with search', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=member1'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('member1@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/`))
|
||||
|
@ -30,6 +30,46 @@ describe('Members API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Can search by case-insensitive name', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=egg'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('member1@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can search by case-insensitive email', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=MEMBER2'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('member2@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Add should fail when passing incorrect email_type query parameter', function () {
|
||||
const member = {
|
||||
name: 'test',
|
||||
|
@ -17,6 +17,7 @@ const expectedProperties = {
|
||||
slug: ['slug'],
|
||||
invites: ['invites', 'meta'],
|
||||
themes: ['themes'],
|
||||
members: ['members', 'meta'],
|
||||
|
||||
post: _(schema.posts)
|
||||
.keys()
|
||||
|
Loading…
Reference in New Issue
Block a user