mirror of
https://github.com/strapi/strapi.git
synced 2025-08-31 20:33:03 +00:00
Merge branch 'master' into chore/strapi-new-improvments
This commit is contained in:
commit
250cfac82d
@ -31,6 +31,7 @@ Filters are used as a suffix of a field name:
|
|||||||
- `ncontains`: Doesn't contain
|
- `ncontains`: Doesn't contain
|
||||||
- `containss`: Contains case sensitive
|
- `containss`: Contains case sensitive
|
||||||
- `ncontainss`: Doesn't contain case sensitive
|
- `ncontainss`: Doesn't contain case sensitive
|
||||||
|
- `null`: Is null/Is not null
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
|
@ -199,6 +199,7 @@ You can also apply different parameters to the query to make more complex querie
|
|||||||
- `<field>_containss`: Contains sensitive.
|
- `<field>_containss`: Contains sensitive.
|
||||||
- `<field>_in`: Matches any value in the array of values.
|
- `<field>_in`: Matches any value in the array of values.
|
||||||
- `<field>_nin`: Doesn't match any value in the array of values.
|
- `<field>_nin`: Doesn't match any value in the array of values.
|
||||||
|
- `<field>_null`: Equals null/Not equals null
|
||||||
|
|
||||||
Return the second decade of users which have an email that contains `@strapi.io` ordered by username.
|
Return the second decade of users which have an email that contains `@strapi.io` ordered by username.
|
||||||
|
|
||||||
@ -695,7 +696,6 @@ module.exports = {
|
|||||||
|
|
||||||
In this example, the policy `isAuthenticated` located in the `users-permissions` plugin will be executed first. Then, the `isOwner` policy located in the `Post` API `./api/post/config/policies/isOwner.js`. Next, it will execute the `logging` policy located in `./config/policies/logging.js`. Finally, the resolver will be executed.
|
In this example, the policy `isAuthenticated` located in the `users-permissions` plugin will be executed first. Then, the `isOwner` policy located in the `Post` API `./api/post/config/policies/isOwner.js`. Next, it will execute the `logging` policy located in `./config/policies/logging.js`. Finally, the resolver will be executed.
|
||||||
|
|
||||||
|
|
||||||
::: note
|
::: note
|
||||||
There is no custom resolver in that case, so it will execute the default resolver (Post.find) provided by the Shadow CRUD feature.
|
There is no custom resolver in that case, so it will execute the default resolver (Post.find) provided by the Shadow CRUD feature.
|
||||||
:::
|
:::
|
||||||
|
@ -28,6 +28,7 @@ Easily filter results according to fields values.
|
|||||||
- `_containss`: Contains case sensitive
|
- `_containss`: Contains case sensitive
|
||||||
- `_in`: Matches any value in the array of values
|
- `_in`: Matches any value in the array of values
|
||||||
- `_nin`: Doesn't match any value in the array of values
|
- `_nin`: Doesn't match any value in the array of values
|
||||||
|
- `_null`: Equals null/Not equals null
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
|
@ -245,6 +245,9 @@ const buildWhereClause = ({ qb, field, operator, value }) => {
|
|||||||
return qb.where(field, 'like', `%${value}%`);
|
return qb.where(field, 'like', `%${value}%`);
|
||||||
case 'ncontainss':
|
case 'ncontainss':
|
||||||
return qb.whereNot(field, 'like', `%${value}%`);
|
return qb.whereNot(field, 'like', `%${value}%`);
|
||||||
|
case 'null': {
|
||||||
|
return value ? qb.whereNull(field) : qb.whereNotNull(field);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);
|
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);
|
||||||
|
@ -438,6 +438,9 @@ const buildWhereClause = ({ field, operator, value }) => {
|
|||||||
$not: new RegExp(val),
|
$not: new RegExp(val),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
case 'null': {
|
||||||
|
return value ? { [field]: { $eq: null } } : { [field]: { $ne: null } };
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);
|
throw new Error(`Unhandled whereClause : ${field} ${operator} ${value}`);
|
||||||
|
@ -25,6 +25,12 @@ const postModel = {
|
|||||||
type: 'biginteger',
|
type: 'biginteger',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'nullable',
|
||||||
|
params: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
connection: 'default',
|
connection: 'default',
|
||||||
name: 'post',
|
name: 'post',
|
||||||
@ -54,8 +60,8 @@ describe('Test Graphql API End to End', () => {
|
|||||||
|
|
||||||
describe('Test CRUD', () => {
|
describe('Test CRUD', () => {
|
||||||
const postsPayload = [
|
const postsPayload = [
|
||||||
{ name: 'post 1', bigint: 1316130638171 },
|
{ name: 'post 1', bigint: 1316130638171, nullable: 'value' },
|
||||||
{ name: 'post 2', bigint: 1416130639261 },
|
{ name: 'post 2', bigint: 1416130639261, nullable: null },
|
||||||
];
|
];
|
||||||
let data = {
|
let data = {
|
||||||
posts: [],
|
posts: [],
|
||||||
@ -69,6 +75,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
post {
|
post {
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +107,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -126,6 +134,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -147,6 +156,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -168,6 +178,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -229,7 +240,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name_in: ['post 1', 'post 2'],
|
name_in: ['post 1', 'post 2', 'post 3'],
|
||||||
},
|
},
|
||||||
postsPayload,
|
postsPayload,
|
||||||
],
|
],
|
||||||
@ -239,6 +250,18 @@ describe('Test Graphql API End to End', () => {
|
|||||||
},
|
},
|
||||||
[postsPayload[0]],
|
[postsPayload[0]],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
nullable_null: true,
|
||||||
|
},
|
||||||
|
[postsPayload[1]],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
nullable_null: false,
|
||||||
|
},
|
||||||
|
[postsPayload[0]],
|
||||||
|
],
|
||||||
])('List posts with where clause %o', async (where, expected) => {
|
])('List posts with where clause %o', async (where, expected) => {
|
||||||
const res = await graphqlQuery({
|
const res = await graphqlQuery({
|
||||||
query: /* GraphQL */ `
|
query: /* GraphQL */ `
|
||||||
@ -246,6 +269,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
posts(where: $where) {
|
posts(where: $where) {
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -278,6 +302,7 @@ describe('Test Graphql API End to End', () => {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
bigint
|
bigint
|
||||||
|
nullable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -360,4 +385,4 @@ describe('Test Graphql API End to End', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -10,6 +10,8 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const AWS = require('aws-sdk');
|
const AWS = require('aws-sdk');
|
||||||
|
|
||||||
|
const trimParam = str => typeof str === "string" ? str.trim() : undefined
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
provider: 'aws-s3',
|
provider: 'aws-s3',
|
||||||
name: 'Amazon Web Service S3',
|
name: 'Amazon Web Service S3',
|
||||||
@ -55,15 +57,15 @@ module.exports = {
|
|||||||
init: (config) => {
|
init: (config) => {
|
||||||
// configure AWS S3 bucket connection
|
// configure AWS S3 bucket connection
|
||||||
AWS.config.update({
|
AWS.config.update({
|
||||||
accessKeyId: config.public,
|
accessKeyId: trimParam(config.public),
|
||||||
secretAccessKey: config.private,
|
secretAccessKey: trimParam(config.private),
|
||||||
region: config.region
|
region: config.region
|
||||||
});
|
});
|
||||||
|
|
||||||
const S3 = new AWS.S3({
|
const S3 = new AWS.S3({
|
||||||
apiVersion: '2006-03-01',
|
apiVersion: '2006-03-01',
|
||||||
params: {
|
params: {
|
||||||
Bucket: config.bucket
|
Bucket: trimParam(config.bucket)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -365,5 +365,33 @@ describe('convertRestQueryParams', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Null', () => {
|
||||||
|
expect(
|
||||||
|
convertRestQueryParams({ 'content.text_null': true })
|
||||||
|
).toMatchObject({
|
||||||
|
where: [
|
||||||
|
{
|
||||||
|
field: 'content.text',
|
||||||
|
operator: 'null',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Not Null', () => {
|
||||||
|
expect(
|
||||||
|
convertRestQueryParams({ 'content.text_null': false })
|
||||||
|
).toMatchObject({
|
||||||
|
where: [
|
||||||
|
{
|
||||||
|
field: 'content.text',
|
||||||
|
operator: 'null',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -66,7 +66,7 @@ const castValueToType = ({ type, value }) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return Boolean(value);
|
||||||
}
|
}
|
||||||
case 'integer':
|
case 'integer':
|
||||||
case 'biginteger':
|
case 'biginteger':
|
||||||
@ -79,6 +79,17 @@ const castValueToType = ({ type, value }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast basic values based on attribute type
|
||||||
|
* @param {Object} options - Options
|
||||||
|
* @param {string} options.type - type of the atribute
|
||||||
|
* @param {*} options.value - value tu cast
|
||||||
|
* @param {string} options.operator - name of operator
|
||||||
|
*/
|
||||||
|
const castValue = ({ type, value, operator}) => {
|
||||||
|
if (operator === 'null') return castValueToType({ type: 'boolean', value })
|
||||||
|
return castValueToType({ type, value})
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} options - Options
|
* @param {Object} options - Options
|
||||||
@ -109,8 +120,8 @@ const buildQuery = ({ model, filters = {}, ...rest }) => {
|
|||||||
|
|
||||||
// cast value or array of values
|
// cast value or array of values
|
||||||
const castedValue = Array.isArray(value)
|
const castedValue = Array.isArray(value)
|
||||||
? value.map(val => castValueToType({ type, value: val }))
|
? value.map(val => castValue({ type, operator, value: val }))
|
||||||
: castValueToType({ type, value: value });
|
: castValue({ type, operator, value: value });
|
||||||
|
|
||||||
return { field, operator, value: castedValue };
|
return { field, operator, value: castedValue };
|
||||||
});
|
});
|
||||||
|
@ -132,6 +132,7 @@ const VALID_OPERATORS = [
|
|||||||
'lte',
|
'lte',
|
||||||
'gt',
|
'gt',
|
||||||
'gte',
|
'gte',
|
||||||
|
'null',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,6 +81,14 @@ const productFixtures = [
|
|||||||
rank: 91,
|
rank: 91,
|
||||||
big_rank: 926372323421,
|
big_rank: 926372323421,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Product 4',
|
||||||
|
description: 'Product description 4',
|
||||||
|
price: null,
|
||||||
|
decimal_field: 12.22,
|
||||||
|
rank: 99,
|
||||||
|
big_rank: 999999999999,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async function createFixtures() {
|
async function createFixtures() {
|
||||||
@ -194,6 +202,35 @@ describe('Filtering API', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Filter null', () => {
|
||||||
|
test('Should return only one match', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/products',
|
||||||
|
qs: {
|
||||||
|
price_null: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(res.body)).toBe(true);
|
||||||
|
expect(res.body.length).toBe(1);
|
||||||
|
expect(res.body[0]).toMatchObject(data.products[3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return three matches', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/products',
|
||||||
|
qs: {
|
||||||
|
price_null: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(res.body)).toBe(true);
|
||||||
|
expect(res.body.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Filter contains insensitive', () => {
|
describe('Filter contains insensitive', () => {
|
||||||
test('Should match with insensitive case', async () => {
|
test('Should match with insensitive case', async () => {
|
||||||
const res1 = await rq({
|
const res1 = await rq({
|
||||||
@ -985,11 +1022,14 @@ describe('Filtering API', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(res.body).toEqual([
|
[
|
||||||
|
data.products[3],
|
||||||
data.products[0],
|
data.products[0],
|
||||||
data.products[2],
|
data.products[2],
|
||||||
data.products[1],
|
data.products[1],
|
||||||
]);
|
].forEach(expectedPost => {
|
||||||
|
expect(res.body).toEqual(expect.arrayContaining([expectedPost]));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user