Merge branch 'master' of github.com:strapi/strapi into add-cypress

This commit is contained in:
soupette 2018-12-11 12:12:53 +01:00
commit 27df580edb
57 changed files with 277 additions and 863 deletions

View File

@ -152,6 +152,12 @@ Follow our [migration guides](https://github.com/strapi/strapi/wiki) on the wiki
Check out our [roadmap](https://portal.productboard.com/strapi) to get informed by the latest feature released and the upcoming ones. You can also give us insights and vote for a specific feature.
## Sponsors
[Become a sponsor](https://opencollective.com/strapi#sponsor) and get your logo on our README on GitHub with a link to your site.
<a href="https://opencollective.com/strapi#contributors"><img src="https://opencollective.com/strapi/tiers/sponsor.svg"/></a>
## License
[MIT License](LICENSE.md) Copyright (c) 2015-2018 [Strapi Solutions](https://strapi.io/).

View File

@ -37,16 +37,6 @@ Find products having a price equal or greater than `3`.
`GET /products?price_gte=3`
#### Relations
You can also use filters into a relation attribute which will be applied to the first level of the request.
Find users having written a post named `Title`.
`GET /users?posts.name=Title`
Find posts written by a user having more than 12 years old.
`GET /posts?author.age_gt=12`
> Note: You can't use filter to have specific results inside relation, like "Find users and only their posts older than yesterday" as example. If you need it, you can modify or create your own service or use [GraphQL](./graphql.md#query-api).
> Warning: this filter isn't available for `upload` plugin
@ -59,8 +49,8 @@ Sort according to a specific field.
Sort users by email.
- ASC: `GET /users?_sort=email:asc`
- DESC: `GET /users?_sort=email:desc`
- ASC: `GET /users?_sort=email:ASC`
- DESC: `GET /users?_sort=email:DESC`
### Limit

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"dependencies": {},
"devDependencies": {
"assert": "~1.3.0",

View File

@ -41,7 +41,7 @@ function LeftMenuLinkContainer({ layout, plugins }) {
}, {});
const linkSections = Object.keys(pluginsSections).map((current, j) => {
const contentTypesToShow = get(layout, 'layout.contentTypesToShow');
const contentTypesToShow = get(layout, 'contentTypesToShow');
const contentTypes = contentTypesToShow
? pluginsSections[current].links.filter(
obj => findIndex(contentTypesToShow, ['destination', obj.destination]) !== -1,

View File

@ -1,3 +1,3 @@
{
"languages": ["en", "ar", "es", "fa", "fr", "de", "it", "ko", "nl", "pl", "pt", "pt-BR", "ru", "tr", "zh", "zh-Hans", "ja"]
"languages": ["en", "ar", "es", "fr", "de", "it", "ko", "nl", "pl", "pt", "pt-BR", "ru", "tr", "zh", "zh-Hans", "ja"]
}

File diff suppressed because one or more lines are too long

View File

@ -37,8 +37,6 @@ export class LocaleToggle extends React.Component { // eslint-disable-line
return 'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/kr.svg';
case 'ja':
return 'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/jp.svg';
case 'fa':
return 'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/ir.svg';
default:
return `https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/flags/4x3/${locale}.svg`;
}

View File

@ -1,141 +0,0 @@
{
"Analytics": "آنالیز",
"Content Manager": "مدیریت محتوا",
"Content Type Builder": "سازنده ی الگوی محتوایی",
"Email": "ایمیل",
"Files Upload": "آپلود فایل",
"HomePage.notification.newsLetter.success": "موفقیت در اشتراک خبر نامه",
"New entry": "رکورد جدید",
"Password": "رمز عبور",
"Provider": "سرویس دهنده",
"ResetPasswordToken": "بازنشانی رمز عبور",
"Role": "نقش",
"Roles & Permissions": "نقش ها و دسترسی ها",
"Settings Manager": "مدیریت تنظیمات",
"Username": "نام کاربری",
"Users": "کاربران",
"Users & Permissions": "کاربران و دسترسی ها",
"app.components.BlockLink.code": "نمونه کد",
"app.components.BlockLink.code.content": "با بررسی و آزمایش پروژه های واقعی توسط جامعه توسعه دهنده Strapi را یاد بگیرید.",
"app.components.BlockLink.documentation": "خواندن مستندات",
"app.components.BlockLink.documentation.content": "مفاهیم، راهنمای مرجع و آموزش ها را فرا بگیرید.",
"app.components.Button.cancel": "کنسل",
"app.components.Button.save": "ذخیره",
"app.components.ComingSoonPage.comingSoon": "بزودی",
"app.components.ComingSoonPage.featuresNotAvailable": "این ویژگی هنوز در حال توسعه است.",
"app.components.DownloadInfo.download": "در حال دانلود...",
"app.components.DownloadInfo.text": "ممکن است دقایقی طول بکشد، با تشکر از شکیبایی شما.",
"app.components.EmptyAttributes.title": "هیچ فیلدی وجود ندارد",
"app.components.HomePage.button.blog": "مشاهده بیشتر در بلاگ",
"app.components.HomePage.button.quickStart": "شروع آموزش سریع",
"app.components.HomePage.community": "جستجوی جامعه توسعه دهنده در اینترنت",
"app.components.HomePage.community.content": "گفتگو با اعضای تیم و توسعه دهندگان در کانال های مختلف ارتباطی",
"app.components.HomePage.create": "ساخت اولین الگوی محتوا",
"app.components.HomePage.createBlock.content.first": "این ",
"app.components.HomePage.createBlock.content.second": " افزونه به شما کمک میکند مدل ساختاری داده های خود را تعریف کنید،اگر شما جدیدا به اینجا مراجعه کردید، پس حتما مارا دنبال کنید ",
"app.components.HomePage.createBlock.content.tutorial": " آموزش.",
"app.components.HomePage.cta": "تایید",
"app.components.HomePage.newsLetter": "برای آگاهی از آخرین اخبار Strapi مشترک خبرنامه شوید.",
"app.components.HomePage.support": "مارا حمایت کنید",
"app.components.HomePage.support.content": "با خرید تی شرت ما میتوانیم به توسعه Strapi ادامه داده و بهترین تجربه را برای شما فراهم کنیم!",
"app.components.HomePage.support.link": "هم اکنون تی شرت خود را دریافت کنید",
"app.components.HomePage.welcome": "خوش آمدید!",
"app.components.HomePage.welcome.again": "خوش آمدید ",
"app.components.HomePage.welcomeBlock.content": "خرسندیم که شما را به عنوان عضوی از جامعه توسعه دهنده بشناسیم، ما هر لحظه به دنبال دریافت بازخورد شما هستیم، در ارسال بازخورد تردید نکنید ",
"app.components.HomePage.welcomeBlock.content.again": "امیدواریم در پروژه خود پیشرفت کنید... میتوانید در مورد آخرین های Strapi مطالعه کنید. بر اساس بازخورد شما ما موثر ترین اقدامات را برای بهبود محصول انجام خواهیم داد.",
"app.components.HomePage.welcomeBlock.content.issues": "مشکلات.",
"app.components.HomePage.welcomeBlock.content.raise": " یا ارتقاء دهید ",
"app.components.ImgPreview.hint": "برای آپلود، فایل خود را بکشید و در این نقطه رها کنید یا {browse}",
"app.components.ImgPreview.hint.browse": "مرور کردن",
"app.components.InputFile.newFile": "افزودن فایل جدید",
"app.components.InputFileDetails.open": "باز کردن در تب جدید",
"app.components.InputFileDetails.originalName": "نام اصلی:",
"app.components.InputFileDetails.remove": "حذف این فایل",
"app.components.InputFileDetails.size": "حجم:",
"app.components.InstallPluginPage.InputSearch.label": " ",
"app.components.InstallPluginPage.InputSearch.placeholder": "جستجوی افزونه... (برای مثال: authentication)",
"app.components.InstallPluginPage.description": "به راحتی برنامه خود را توسعه دهید.",
"app.components.InstallPluginPage.helmet": "فروشگاه - افزونه ها",
"app.components.InstallPluginPage.plugin.support-us.description": "با خرید تی شرت ما میتوانیم به توسعه Strapi ادامه داده و بهترین تجربه را برای شما فراهم کنیم!",
"app.components.InstallPluginPage.title": "فروشگاه - افزونه ها",
"app.components.InstallPluginPopup.downloads": "دانلود",
"app.components.InstallPluginPopup.navLink.avis": "مشاهده",
"app.components.InstallPluginPopup.navLink.changelog": "تغییرات",
"app.components.InstallPluginPopup.navLink.description": "توضیحات",
"app.components.InstallPluginPopup.navLink.faq": "پرسش و پاسخ",
"app.components.InstallPluginPopup.navLink.screenshots": "تصاویر",
"app.components.InstallPluginPopup.noDescription": "توضیحاتی وجود ندارد",
"app.components.LeftMenuFooter.poweredBy": "قدرت گرفته از ",
"app.components.LeftMenuLinkContainer.configuration": "پیکربندی",
"app.components.LeftMenuLinkContainer.general": "عمومی",
"app.components.LeftMenuLinkContainer.installNewPlugin": "فروشگاه",
"app.components.LeftMenuLinkContainer.listPlugins": "افزونه ها",
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "هیچ افزونه ای نصب نشده است",
"app.components.LeftMenuLinkContainer.plugins": "افزونه ها",
"app.components.ListPluginsPage.description": "فهرست افزونه های نصب شده در پروژه.",
"app.components.ListPluginsPage.helmet.title": "فهرست افزونه ها",
"app.components.ListPluginsPage.title": "افزونه ها",
"app.components.NotFoundPage.back": "بازگشت به صفحه اصلی",
"app.components.NotFoundPage.description": "یافت نشد",
"app.components.Official": "رسمی",
"app.components.PluginCard.Button.label.download": "دانلود",
"app.components.PluginCard.Button.label.install": "قبلا نصب شده است",
"app.components.PluginCard.Button.label.support": "حمایت از ما",
"app.components.PluginCard.compatible": "سازگار با برنامه ی شما",
"app.components.PluginCard.compatibleCommunity": "سازگار با جامعه ی توسعه دهنده",
"app.components.PluginCard.more-details": "اطلاعات بیشتر",
"app.components.PluginCard.price.free": "ریگان",
"app.components.listPlugins.button": "افزودن افزونه جدید",
"app.components.listPlugins.title.none": "هیچ افزونه ای نصب نشده است",
"app.components.listPlugins.title.plural": "{number} افزونه نصب شده",
"app.components.listPlugins.title.singular": "{number} افزونه نصب شده",
"app.components.listPluginsPage.deletePlugin.error": "خطا در هنگام حذف این افزونه",
"app.utils.SelectOption.defaultMessage": " ",
"app.utils.defaultMessage": " ",
"app.utils.placeholder.defaultMessage": " ",
"components.AutoReloadBlocker.description": "فایل را باز کرده و این ویژگی را فعال کنید.",
"components.AutoReloadBlocker.header": "بارگذاری مجدد برای این افزونه ضروری است.",
"components.ErrorBoundary.title": "خطایی رخ داده است...",
"components.Input.error.attribute.key.taken": "این مقدار در حال حاضر وجود دارد",
"components.Input.error.attribute.sameKeyAndName": "نمیتواند برابر باشد",
"components.Input.error.attribute.taken": "نام فیلد در حال حاضر وجود دارد",
"components.Input.error.contentTypeName.taken": "این نام هم اکنون وجود دارد",
"components.Input.error.custom-error": "{errorMessage} ",
"components.Input.error.validation.email": "این مقدار پست الکترونیک صحیح نیست",
"components.Input.error.validation.json": "این مقدار با فرمت استاندارد JSON مطابقت ندارد",
"components.Input.error.validation.max": "این مقدار زیاد است.",
"components.Input.error.validation.maxLength": "این مقدار طولانی است.",
"components.Input.error.validation.min": "این مقدار کوچک است.",
"components.Input.error.validation.minLength": "طول این مقدار کم است.",
"components.Input.error.validation.minSupMax": "نمی تواند بالاتر باشد",
"components.Input.error.validation.regex": "این مقدار با عبارت منظم مطابقت ندارد.",
"components.Input.error.validation.required": "این مقدار ضروری است.",
"components.ListRow.empty": "داده ای جهت نمایش وجود ندارد.",
"components.OverlayBlocker.description": "اقدام مورد نظر نیازمند راه اندازی مجدد سرور است. لطفا منتظر بمانید.",
"components.OverlayBlocker.title": "در انتظار راه اندازی مجدد...",
"components.PageFooter.select": "تعداد سطر در هر صفحه",
"components.ProductionBlocker.description": "به جهت موارد امنیتی ما باید این افزونه را در محیط های دیگر غیرفعال کنیم.",
"components.ProductionBlocker.header": "این افزونه فقط در حالت توسعه در دسترس است.",
"components.Wysiwyg.ToggleMode.markdown": "تبدیل به حالت markdown",
"components.Wysiwyg.ToggleMode.preview": "تبدیل به حالت نمایشی",
"components.Wysiwyg.collapse": "کوچک کردن",
"components.Wysiwyg.selectOptions.H1": "عنوان H1",
"components.Wysiwyg.selectOptions.H2": "عنوان H2",
"components.Wysiwyg.selectOptions.H3": "عنوان H3",
"components.Wysiwyg.selectOptions.H4": "عنوان H4",
"components.Wysiwyg.selectOptions.H5": "عنوان H5",
"components.Wysiwyg.selectOptions.H6": "عنوان H6",
"components.Wysiwyg.selectOptions.title": "افزدون عنوان",
"components.WysiwygBottomControls.charactersIndicators": "حروف",
"components.WysiwygBottomControls.fullscreen": "تمام صفحه",
"components.WysiwygBottomControls.uploadFiles": "کشیدن و رها کردن فایل، خواندن از حافظه یا {browse}.",
"components.WysiwygBottomControls.uploadFiles.browse": "انتخاب کنید",
"components.popUpWarning.button.cancel": "کنسل",
"components.popUpWarning.button.confirm": "تایید",
"components.popUpWarning.message": "آیا از حذف این مقدار اطمینان دارید؟",
"components.popUpWarning.title": "لطفا تایید کنید",
"notification.error": "خطایی رخ داده است",
"notification.error.layout": "خطا در بازیابی طرح",
"request.error.model.unknown": "این مدل وجود ندارد",
"app.utils.delete": "حذف"
}

View File

@ -1,6 +1,6 @@
{
"name": "strapi-admin",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Strapi Admin",
"repository": {
"type": "git",
@ -31,8 +31,8 @@
},
"devDependencies": {
"sanitize.css": "^4.1.0",
"strapi-helper-plugin": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16",
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-admin",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate the default admin panel for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,8 +15,8 @@
"dependencies": {
"fs-extra": "^4.0.1",
"lodash": "^4.17.5",
"strapi-admin": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.15"
"strapi-admin": "3.0.0-alpha.16",
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-api",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate an API for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,5 +1,4 @@
'use strict';
/* global <%= globalID %> */
/**
* <%= filename %> service
@ -10,6 +9,9 @@
// Public dependencies.
const _ = require('lodash');
// Strapi utilities.
const utils = require('strapi-hook-bookshelf/lib/utils/');
module.exports = {
/**
@ -19,8 +21,6 @@ module.exports = {
*/
fetchAll: (params) => {
// Get model hook
const hook = strapi.hook[<%= globalID %>.orm];
// Convert `params` object to filters compatible with Bookshelf.
const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params);
// Select field to populate.
@ -29,18 +29,22 @@ module.exports = {
.map(ast => ast.alias);
return <%= globalID %>.query(function(qb) {
// Generate match stage.
hook.load().generateMatchStage(qb)(<%= globalID %>, filters);
if (_.has(filters, 'start')) qb.offset(filters.start);
if (_.has(filters, 'limit')) qb.limit(filters.limit);
if (!_.isEmpty(filters.sort)) {
if (filters.sort.key) {
qb.orderBy(filters.sort.key, filters.sort.order);
_.forEach(filters.where, (where, key) => {
if (_.isArray(where.value) && where.symbol !== 'IN') {
for (const value in where.value) {
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value])
}
} else {
qb.orderBy(filters.sort);
qb.where(key, where.symbol, where.value);
}
});
if (filters.sort) {
qb.orderBy(filters.sort.key, filters.sort.order);
}
qb.offset(filters.start);
qb.limit(filters.limit);
}).fetchAll({
withRelated: populate
});

View File

@ -1,5 +1,4 @@
'use strict';
/* global <%= globalID %> */
/**
* <%= filename %> service
@ -10,8 +9,6 @@
// Public dependencies.
const _ = require('lodash');
const { models: { mergeStages } } = require('strapi-utils');
module.exports = {
/**
@ -20,24 +17,22 @@ module.exports = {
* @return {Promise}
*/
fetchAll: (params, next, { populate } = {}) => {
fetchAll: (params) => {
// Convert `params` object to filters compatible with Mongo.
const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params);
const hook = strapi.hook[<%= globalID %>.orm];
// Generate stages.
const populateStage = hook.load().generateLookupStage(<%= globalID %>, { whitelistedPopulate: populate }); // Nested-Population
const matchStage = hook.load().generateMatchStage(<%= globalID %>, filters); // Nested relation filter
const aggregateStages = mergeStages(populateStage, matchStage);
// Select field to populate.
const populate = <%= globalID %>.associations
.filter(ast => ast.autoPopulate !== false)
.map(ast => ast.alias)
.join(' ');
const result = <%= globalID %>.aggregate(aggregateStages)
return <%= globalID %>
.find()
.where(filters.where)
.sort(filters.sort)
.skip(filters.start)
.limit(filters.limit);
if (_.has(filters, 'start')) result.skip(filters.start);
if (_.has(filters, 'limit')) result.limit(filters.limit);
if (!_.isEmpty(filters.sort)) result.sort(filters.sort);
return result;
.limit(filters.limit)
.populate(populate);
},
/**

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-controller",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate a controller for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-model",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate a model for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -56,7 +56,6 @@ module.exports = scope => {
'dependencies': Object.assign({}, {
'lodash': '^4.17.5',
'strapi': getDependencyVersion(cliPkg, 'strapi'),
'strapi-utils': getDependencyVersion(cliPkg, 'strapi'),
[scope.client.connector]: getDependencyVersion(cliPkg, 'strapi'),
}, additionalsDependencies, {
[scope.client.module]: scope.client.version

View File

@ -38,9 +38,6 @@ module.exports = {
'.editorconfig': {
copy: 'editorconfig'
},
'.npmignore': {
copy: 'npmignore'
},
'.gitignore': {
copy: 'gitignore'
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-new",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate a new Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -19,7 +19,7 @@
"listr": "^0.14.1",
"lodash": "^4.17.5",
"ora": "^2.1.0",
"strapi-utils": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.16",
"uuid": "^3.1.0"
},
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-plugin",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate an plugin for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-policy",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate a policy for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-service",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Generate a service for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Master of ceremonies for the Strapi generators.",
"homepage": "http://strapi.io",
"keywords": [
@ -17,7 +17,7 @@
"fs-extra": "^4.0.0",
"lodash": "^4.17.5",
"reportback": "^2.0.1",
"strapi-utils": "3.0.0-alpha.15"
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-helper-plugin",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Helper for Strapi plugins development",
"engines": {
"node": ">= 10.0.0",

View File

@ -677,11 +677,24 @@ module.exports = function(strapi) {
}
};
const table = _.get(manyRelations, 'collectionName')
|| utilsModels.getCollectionName(
collection.attributes[manyRelations.via],
manyRelations
);
const table = _.get(manyRelations, 'collectionName') ||
_.map(
_.sortBy(
[
collection.attributes[
manyRelations.via
],
manyRelations
],
'collection'
),
table => {
return _.snakeCase(
// eslint-disable-next-line prefer-template
pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via)
);
}
).join('__');
await handler(table, attributes);
}
@ -800,11 +813,24 @@ module.exports = function(strapi) {
strapi.plugins[details.plugin].models[details.collection]:
strapi.models[details.collection];
const collectionName = _.get(details, 'collectionName')
|| utilsModels.getCollectionName(
collection.attributes[details.via],
details,
);
const collectionName = _.get(details, 'collectionName') ||
_.map(
_.sortBy(
[
collection.attributes[
details.via
],
details
],
'collection'
),
table => {
return _.snakeCase(
// eslint-disable-next-line prefer-template
pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via)
);
}
).join('__');
const relationship = _.clone(
collection.attributes[details.via]

View File

@ -35,89 +35,6 @@ module.exports = {
return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
},
generateMatchStage: function (qb) {
return (strapiModel, filters) => {
if (!filters) {
return undefined;
}
// 1st level deep filter
if (filters.where) {
this.generateMatchStage(qb)(strapiModel, { relations: filters.where });
}
// 2nd+ level deep filter
_.forEach(filters.relations, (value, key) => {
if (key !== 'relations') {
const association = strapiModel.associations.find(a => a.alias === key);
if (!association) {
const fieldKey = `${strapiModel.collectionName}.${key}`;
if (_.isArray(value.value) && value.symbol !== 'IN') {
for (let value in value.value) {
if (typeof value === 'string') {
value = {
value,
symbol: '='
};
}
qb[value ? 'where' : 'orWhere'](fieldKey, value.symbol, value.value[value]);
}
} else {
if (typeof value === 'string') {
value = {
value,
symbol: '='
};
}
qb.where(fieldKey, value.symbol, value.value);
}
} else {
const model = association.plugin ?
strapi.plugins[association.plugin].models[association.model || association.collection] :
strapi.models[association.model || association.collection];
const relationTable = model.collectionName;
qb.distinct();
if (association.nature === 'manyToMany') {
// Join on both ends
qb.innerJoin(
association.tableCollectionName,
`${association.tableCollectionName}.${strapiModel.info.name}_${strapiModel.primaryKey}`,
`${strapiModel.collectionName}.${strapiModel.primaryKey}`,
);
qb.innerJoin(
relationTable,
`${association.tableCollectionName}.${strapiModel.attributes[key].attribute}_${strapiModel.attributes[key].column}`,
`${relationTable}.${model.primaryKey}`,
);
} else {
const externalKey = association.type === 'collection'
? `${relationTable}.${association.via}`
: `${relationTable}.${model.primaryKey}`;
const internalKey = association.type === 'collection'
? `${strapiModel.collectionName}.${strapiModel.primaryKey}`
: `${strapiModel.collectionName}.${association.alias}`;
qb.innerJoin(relationTable, externalKey, internalKey);
}
if (_.isPlainObject(value)) {
this.generateMatchStage(qb)(
model,
{ relations: value.value }
);
}
}
} else {
this.generateMatchStage(qb)(strapiModel, { relations: value });
}
});
};
},
findOne: async function (params, populate) {
const record = await this
.forge({

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-bookshelf",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Bookshelf hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -21,8 +21,8 @@
"lodash": "^4.17.5",
"pluralize": "^6.0.0",
"rimraf": "^2.6.2",
"strapi-hook-knex": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.15"
"strapi-hook-knex": "3.0.0-alpha.16",
"strapi-utils": "3.0.0-alpha.16"
},
"strapi": {
"dependencies": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-ejs",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "EJS hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-knex",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Knex hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -17,7 +17,6 @@ const { models: utilsModels } = require('strapi-utils');
// Local helpers.
const utils = require('./utils/');
const _utils = utils();
const relations = require('./relations');
@ -522,13 +521,6 @@ module.exports = function (strapi) {
}
return result;
},
postProcessValue: (value) => {
if (_.isArray(value)) {
return value.map(_utils.valueToId);
}
return _utils.valueToId(value);
}
}, relations);

View File

@ -10,156 +10,11 @@ const _ = require('lodash');
// Utils
const { models: { getValuePrimaryKey } } = require('strapi-utils');
const buildTempFieldPath = field => {
return `__${field}`;
};
const restoreRealFieldPath = (field, prefix) => {
return `${prefix}${field}`;
};
module.exports = {
getModel: function (model, plugin) {
return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
},
generateLookupStage: function (strapiModel, { whitelistedPopulate = null, prefixPath = '' } = {}) {
return strapiModel.associations
.filter(ast => {
if (whitelistedPopulate) {
return _.includes(whitelistedPopulate, ast.alias);
}
return ast.autoPopulate;
})
.reduce((acc, ast) => {
const model = ast.plugin
? strapi.plugins[ast.plugin].models[ast.collection || ast.model]
: strapi.models[ast.collection || ast.model];
const from = model.collectionName;
const isDominantAssociation =
(ast.dominant && ast.nature === 'manyToMany') || !!ast.model;
const _localField =
!isDominantAssociation || ast.via === 'related' ? '_id' : ast.alias;
const localField = `${prefixPath}${_localField}`;
const foreignField = ast.filter
? `${ast.via}.ref`
: isDominantAssociation
? '_id'
: ast.via;
// Add the juncture like the `.populate()` function
const asTempPath = buildTempFieldPath(ast.alias, prefixPath);
const asRealPath = restoreRealFieldPath(ast.alias, prefixPath);
acc.push({
$lookup: {
from,
localField,
foreignField,
as: asTempPath,
},
});
// Unwind the relation's result if only one is expected
if (ast.type === 'model') {
acc.push({
$unwind: {
path: `$${asTempPath}`,
preserveNullAndEmptyArrays: true,
},
});
}
// Preserve relation field if it is empty
acc.push({
$addFields: {
[asRealPath]: {
$ifNull: [`$${asTempPath}`, null],
},
},
});
// Remove temp field
acc.push({
$project: {
[asTempPath]: 0,
},
});
return acc;
}, []);
},
generateMatchStage: function (strapiModel, filters, { prefixPath = '' } = {}) {
if (!filters) {
return undefined;
}
let acc = [];
// 1st level deep filter
if (filters.where) {
acc.push(
...this.generateMatchStage(
strapiModel,
{ relations: filters.where },
{ prefixPath }
)
);
}
// 2nd+ level deep filter
_.forEach(filters.relations, (value, key) => {
if (key !== 'relations') {
const nextPrefixedPath = `${prefixPath}${key}.`;
const association = strapiModel.associations.find(a => a.alias === key);
if (!association) {
acc.push({
$match: { [`${prefixPath}${key}`]: value },
});
} else {
const model = association.plugin
? strapi.plugins[association.plugin].models[
association.collection || association.model
]
: strapi.models[association.collection || association.model];
// Generate lookup for this relation
acc.push(
...this.generateLookupStage(strapiModel, {
whitelistedPopulate: [key],
prefixPath,
})
);
// If it's an object re-run the same function with this new value until having either a primitive value or an array.
if (_.isPlainObject(value)) {
acc.push(
...this.generateMatchStage(
model,
{ relations: value },
{
prefixPath: nextPrefixedPath,
}
)
);
}
}
} else {
acc.push(
...this.generateMatchStage(strapiModel, { relations: value }, { prefixPath })
);
}
});
return acc;
},
update: async function (params) {
const virtualFields = [];
const response = await this
@ -242,7 +97,7 @@ module.exports = {
acc[current] = params.values[current];
} else if (response[current] && _.isArray(response[current]) && current !== 'id') {
// Records to add in the relation.
const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
(a[this.primaryKey] || a).toString() === (b[this.primaryKey] || b).toString()
);

View File

@ -4,23 +4,18 @@
* Module dependencies
*/
// Public node modules.
const mongoose = require('mongoose');
const Mongoose = mongoose.Mongoose;
/**
* Convert MongoDB ID to the stringify version as GraphQL throws an error if not.
*
* Refer to: https://github.com/graphql/graphql-js/commit/3521e1429eec7eabeee4da65c93306b51308727b#diff-87c5e74dd1f7d923143e0eee611f598eR183
*/
mongoose.Types.ObjectId.prototype.valueOf = function () {
return this.toString();
};
module.exports = (mongoose = new Mongoose()) => {
mongoose.Schema.Types.Decimal = require('mongoose-float').loadType(mongoose, 2);
mongoose.Schema.Types.Float = require('mongoose-float').loadType(mongoose, 20);
const Decimal = require('mongoose-float').loadType(mongoose, 2);
const Float = require('mongoose-float').loadType(mongoose, 20);
/**
* Convert MongoDB ID to the stringify version as GraphQL throws an error if not.
*
* Refer to: https://github.com/graphql/graphql-js/commit/3521e1429eec7eabeee4da65c93306b51308727b#diff-87c5e74dd1f7d923143e0eee611f598eR183
*/
mongoose.Types.ObjectId.prototype.valueOf = function () {
return this.toString();
};
return {
convertType: mongooseType => {
@ -37,9 +32,9 @@ module.exports = (mongoose = new Mongoose()) => {
case 'timestamp':
return Date;
case 'decimal':
return Decimal;
return 'Decimal';
case 'float':
return Float;
return 'Float';
case 'json':
return 'Mixed';
case 'biginteger':
@ -55,17 +50,6 @@ module.exports = (mongoose = new Mongoose()) => {
return 'String';
default:
}
},
valueToId: function (value) {
return this.isMongoId(value)
? mongoose.Types.ObjectId(value)
: value;
},
isMongoId: function (value) {
// Here we don't use mongoose.Types.ObjectId.isValid method because it's a weird check,
// it returns for instance true for any integer value ¯\_(ツ)_/¯
const hexadecimal = /^[0-9A-F]+$/i;
return hexadecimal.test(value) && value.length === 24;
}
};
};

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-mongoose",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Mongoose hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -20,7 +20,7 @@
"mongoose-float": "^1.0.3",
"pluralize": "^6.0.0",
"rimraf": "^2.6.2",
"strapi-utils": "3.0.0-alpha.15"
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-redis",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Redis hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -19,7 +19,7 @@
"lodash": "^4.17.5",
"rimraf": "^2.6.2",
"stack-trace": "0.0.10",
"strapi-utils": "3.0.0-alpha.15"
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-lint",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Strapi eslint and prettier configurations",
"directories": {
"lib": "lib"

View File

@ -1,6 +1,6 @@
{
"name": "strapi-middleware-views",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Views middleware to enable server-side rendering for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-manager",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "A powerful UI to easily manage your data.",
"strapi": {
"name": "Content Manager",
@ -26,7 +26,7 @@
"draft-js": "^0.10.5",
"react-select": "^1.2.1",
"showdown": "^1.8.6",
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"dependencies": {
"pluralize": "^7.0.0"

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-type-builder",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Strapi plugin to create content type (API).",
"strapi": {
"name": "Content Type Builder",
@ -24,11 +24,11 @@
"dependencies": {
"immutable": "^3.8.2",
"pluralize": "^7.0.0",
"strapi-generate": "3.0.0-alpha.15",
"strapi-generate-api": "3.0.0-alpha.15"
"strapi-generate": "3.0.0-alpha.16",
"strapi-generate-api": "3.0.0-alpha.16"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-email",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Email",
@ -22,11 +22,11 @@
"prepublishOnly": "IS_MONOREPO=true npm run build"
},
"dependencies": {
"strapi-provider-email-sendmail": "3.0.0-alpha.15"
"strapi-provider-email-sendmail": "3.0.0-alpha.16"
},
"devDependencies": {
"react-copy-to-clipboard": "5.0.1",
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-graphql",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "This is the description of the plugin.",
"strapi": {
"name": "graphql",
@ -30,7 +30,7 @@
"graphql-type-json": "^0.2.1",
"graphql-type-datetime": "^0.2.2",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-alpha.15"
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"name": "A Strapi developer",

View File

@ -25,23 +25,6 @@ module.exports = {
}, {});
},
convertToQuery: function(params) {
const result = {};
_.forEach(params, (value, key) => {
if (_.isPlainObject(value)) {
const flatObject = this.convertToQuery(value);
_.forEach (flatObject, (_value, _key) => {
result[`${key}.${_key}`] = _value;
});
} else {
result[key] = value;
}
});
return result;
},
/**
* Security to avoid infinite limit.
*
@ -192,15 +175,13 @@ module.exports = {
// Plural.
return async (ctx, next) => {
const queryOpts = {};
queryOpts.params = this.amountLimiting(ctx.params);
queryOpts.query = Object.assign(
{},
this.convertToParams(_.omit(queryOpts.params, 'where')),
this.convertToQuery(queryOpts.params.where)
ctx.params = this.amountLimiting(ctx.params);
ctx.query = Object.assign(
this.convertToParams(_.omit(ctx.params, 'where')),
ctx.params.where,
);
return controller(Object.assign({}, ctx, queryOpts, { send: ctx.send }), next, { populate: [] });
return controller(ctx, next);
};
})();
@ -275,12 +256,8 @@ module.exports = {
// Resolver can be a function. Be also a native resolver or a controller's action.
if (_.isFunction(resolver)) {
context.query = this.convertToParams(options);
context.params = this.amountLimiting(options);
context.query = Object.assign(
{},
this.convertToParams(_.omit(options, 'where')),
this.convertToQuery(options.where)
);
if (isController) {
const values = await resolver.call(null, context);

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-settings-manager",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Strapi plugin to manage settings.",
"strapi": {
"name": "Settings Manager",
@ -25,7 +25,7 @@
"devDependencies": {
"flag-icon-css": "^2.8.0",
"react-select": "^1.0.0-rc.5",
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-upload",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Files Upload",
@ -23,12 +23,12 @@
},
"dependencies": {
"react-copy-to-clipboard": "^5.0.1",
"strapi-provider-upload-local": "3.0.0-alpha.15",
"strapi-provider-upload-local": "3.0.0-alpha.16",
"stream-to-array": "^2.3.0",
"uuid": "^3.2.1"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"author": {
"name": "A Strapi developer",

View File

@ -3,14 +3,23 @@ const _ = require('lodash');
module.exports = {
find: async function (params = {}, populate) {
const hook = strapi.hook[this.orm];
const records = await this.query((qb) => {
// Generate match stage.
hook.load().generateMatchStage(qb)(this, params);
if (_.has(params, 'start')) qb.offset(params.start);
if (_.has(params, 'limit')) qb.limit(params.limit);
if (!_.isEmpty(params.sort)) {
const records = await this.query(function(qb) {
_.forEach(params.where, (where, key) => {
if (_.isArray(where.value)) {
for (const value in where.value) {
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
}
} else {
qb.where(key, where.symbol, where.value);
}
});
if (params.start) {
qb.offset(params.start);
}
if (params.limit) {
qb.limit(params.limit);
}
if (params.sort) {
if (params.sort.key) {
qb.orderBy(params.sort.key, params.sort.order);
} else {

View File

@ -1,22 +1,14 @@
const _ = require('lodash');
const { models: { mergeStages } } = require('strapi-utils');
module.exports = {
find: async function (filters = {}, populate) {
const hook = strapi.hook[this.orm];
// Generate stages.
const populateStage = hook.load().generateLookupStage(this, { whitelistedPopulate: populate }); // Nested-Population
const matchStage = hook.load().generateMatchStage(this, filters); // Nested relation filter
const aggregateStages = mergeStages(populateStage, matchStage);
const result = this.aggregate(aggregateStages);
if (_.has(filters, 'start') && filters.start) result.skip(filters.start);
if (_.has(filters, 'limit') && filters.limit) result.limit(filters.limit);
if (_.has(filters, 'sort') && filters.sort) result.sort(filters.sort);
return result;
find: async function (params = {}, populate) {
return this
.find(params.where)
.limit(Number(params.limit))
.sort(params.sort)
.skip(Number(params.skip))
.populate(populate || this.associations.map(x => x.alias).join(' '))
.lean();
},
count: async function (params = {}) {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-users-permissions",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Protect your API with a full-authentication process based on JWT",
"strapi": {
"name": "Roles & Permissions",
@ -29,11 +29,11 @@
"koa2-ratelimit": "^0.6.1",
"purest": "^2.0.1",
"request": "^2.83.0",
"strapi-utils": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.16",
"uuid": "^3.1.0"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.15"
"strapi-helper-plugin": "3.0.0-alpha.16"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-amazon-ses",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Amazon SES provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-mailgun",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Mailgun provider for strapi email plugin",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-sendgrid",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Sendgrid provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-sendmail",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Sendmail provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-aws-s3",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "AWS S3 provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-cloudinary",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Cloudinary provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-local",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Local provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-rackspace",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Rackspace provider for strapi upload",
"main": "./lib",
"scripts": {

View File

@ -9,10 +9,11 @@ const path = require('path');
// Public node modules.
const _ = require('lodash');
const pluralize = require('pluralize');
// Constants
const ORDERS = ['ASC', 'DESC'];
// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
const isNumeric = (value) => {
return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
};
/* eslint-disable prefer-template */
/*
@ -308,16 +309,6 @@ module.exports = {
return _.get(strapi.models, collectionIdentity.toLowerCase() + '.orm');
},
/**
* Return table name for a collection many-to-many
*/
getCollectionName: (associationA, associationB) => {
return [associationA, associationB]
.sort((a, b) => a.collection < b.collection ? -1 : 1)
.map(table => _.snakeCase(`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`))
.join('__');
},
/**
* Define associations key to models
*/
@ -347,7 +338,7 @@ module.exports = {
// Build associations object
if (association.hasOwnProperty('collection') && association.collection !== '*') {
const ast = {
definition.associations.push({
alias: key,
type: 'collection',
collection: association.collection,
@ -357,13 +348,7 @@ module.exports = {
dominant: details.dominant !== true,
plugin: association.plugin || undefined,
filter: details.filter,
};
if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
ast.tableCollectionName = this.getCollectionName(association, details);
}
definition.associations.push(ast);
});
} else if (association.hasOwnProperty('model') && association.model !== '*') {
definition.associations.push({
alias: key,
@ -431,32 +416,9 @@ module.exports = {
return _.findKey(strapi.models[association.model || association.collection].attributes, {via: attribute});
},
mergeStages: (...stages) => {
return _.unionWith(...stages, _.isEqual);
},
convertParams: function (entity, params) {
const { model, models, convertor, postProcessValue } = this.prepareStage(
entity,
params
);
const _filter = this.splitPrimitiveAndRelationValues(params);
// Execute Steps in the given order
return _.flow([
this.processValues({ model, models, convertor, postProcessValue }),
this.processPredicates({ model, models, convertor }),
this.processGeneratedResults(),
this.mergeWhereAndRelationPayloads()
])(_filter);
},
prepareStage: function (entity, params) {
convertParams: (entity, params) => {
if (!entity) {
throw new Error(
'You can\'t call the convert params method without passing the model\'s name as a first argument.'
);
throw new Error('You can\'t call the convert params method without passing the model\'s name as a first argument.');
}
// Remove the source params (that can be sent from the ctm plugin) since it is not a filter
@ -464,229 +426,84 @@ module.exports = {
delete params.source;
}
const modelName = entity.toLowerCase();
const models = this.getStrapiModels();
const model = models[modelName];
const model = entity.toLowerCase();
if (!model) {
throw new Error(`The model ${modelName} can't be found.`);
const models = _.assign(_.clone(strapi.models), Object.keys(strapi.plugins).reduce((acc, current) => {
_.assign(acc, _.get(strapi.plugins[current], ['models'], {}));
return acc;
}, {}));
if (!models.hasOwnProperty(model)) {
return this.log.error(`The model ${model} can't be found.`);
}
if (!model.orm) {
throw new Error(
`Impossible to determine the ORM used for the model ${modelName}.`
);
const client = models[model].client;
const connector = models[model].orm;
if (!connector) {
throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
}
const hook = strapi.hook[model.orm];
const convertor = hook.load().getQueryParams;
const postProcessValue = hook.load().postProcessValue || _.identity;
return {
models,
model,
hook,
convertor,
postProcessValue,
const convertor = strapi.hook[connector].load().getQueryParams;
const convertParams = {
where: {},
sort: '',
start: 0,
limit: 100
};
},
getStrapiModels: function() {
return {
...strapi.models,
...Object.keys(strapi.plugins).reduce(
(acc, pluginName) => ({
...acc,
..._.get(strapi.plugins[pluginName], 'models', {}),
}),
{}
),
};
},
splitPrimitiveAndRelationValues: function(_query) {
const result = _.reduce(
_query,
(acc, value, key) => {
if (_.startsWith(key, '_')) {
acc[key] = value;
} else if (!_.includes(key, '.')) {
acc.where[key] = value;
} else {
_.set(acc.relations, this.injectRelationInKey(key), value);
_.forEach(params, (value, key) => {
let result;
let formattedValue;
let modelAttributes = models[model]['attributes'];
let fieldType;
// Get the field type to later check if it's a string before number conversion
if (modelAttributes[key]) {
fieldType = modelAttributes[key]['type'];
} else {
// Remove the filter keyword at the end
let splitKey = key.split('_').slice(0,-1);
splitKey = splitKey.join('_');
if (modelAttributes[splitKey]) {
fieldType = modelAttributes[splitKey]['type'];
}
return acc;
},
{
where: {},
relations: {},
sort: '',
start: 0,
limit: 100,
}
);
return result;
},
// Check if the value is a valid candidate to be converted to a number value
if (fieldType !== 'string') {
formattedValue = isNumeric(value)
? _.toNumber(value)
: value;
} else {
formattedValue = value;
}
injectRelationInKey: function (key) {
const numberOfRelations = key.match(/\./gi).length - 1;
const relationStrings = _.times(numberOfRelations, _.constant('relations'));
return _.chain(key)
.split('.')
.zip(relationStrings)
.flatten()
.compact()
.join('.')
.value();
},
if (_.includes(['_start', '_limit'], key)) {
result = convertor(formattedValue, key);
} else if (key === '_sort') {
const [attr, order = 'ASC'] = formattedValue.split(':');
result = convertor(order, key, attr);
} else {
const suffix = key.split('_');
// Mysql stores boolean as 1 or 0
if (client === 'mysql' && _.get(models, [model, 'attributes', suffix, 'type']) === 'boolean') {
formattedValue = value === 'true' ? '1' : '0';
}
transformFilter: function (filter, iteratee) {
if (!_.isArray(filter) && !_.isPlainObject(filter)) {
return filter;
}
let type;
return _.transform(filter, (updatedFilter, value, key) => {
const updatedValue = iteratee(value, key);
updatedFilter[key] = this.transformFilter(updatedValue, iteratee);
return updatedFilter;
if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in'], _.last(suffix))) {
type = `_${_.last(suffix)}`;
key = _.dropRight(suffix).join('_');
} else {
type = '=';
}
result = convertor(formattedValue, type, key);
}
_.set(convertParams, result.key, result.value);
});
},
processValues: function ({ model, models, convertor, postProcessValue }) {
return filter => {
let parentModel = model;
return this.transformFilter(filter, (value, key) => {
const field = this.getFieldFromKey(key, parentModel);
if (!field) {
return this.processMeta(value, key, {
field,
client: model.client,
model,
convertor,
});
}
if (field.collection || field.model) {
parentModel = models[field.collection || field.model];
}
return postProcessValue(
this.processValue(value, key, { field, client: model.client, model })
);
});
};
},
getFieldFromKey: function (key, model) {
let field;
// Primary key is a unique case because it doesn't belong to the model's attributes
if (key === model.primaryKey) {
field = {
type: 'ID', // Just in case
};
} else if (model.attributes[key]) {
field = model.attributes[key];
} else if (typeof key === 'string') {
// Remove the filter keyword at the end
let splitKey = key.split('_').slice(0, -1);
splitKey = splitKey.join('_');
if (model.attributes[splitKey]) {
field = model.attributes[splitKey];
}
}
return field;
},
processValue: function (value, key, { field, client }) {
if (field.type === 'boolean' && client === 'mysql') {
return value === 'true' ? '1' : '0';
}
return value;
},
processMeta: function (value, key, { convertor, model }) {
if (_.includes(['_start', '_limit'], key)) {
return convertor(value, key);
} else if (key === '_sort') {
return this.processSortMeta(value, key, { convertor, model });
}
return value;
},
processSortMeta: function (value, key, { convertor, model }) {
const [attr, order = 'ASC'] = value.split(':');
if (!_.includes(ORDERS, order)) {
throw new Error(
`Unkown order value: "${order}", available values are: ${ORDERS.join(
', '
)}`
);
}
const field = this.getFieldFromKey(attr, model);
if (!field) {
throw new Error(`Unkown field: "${attr}"`);
}
return convertor(order, key, attr);
},
processPredicates: function ({ model, models, convertor }) {
return filter => {
let parentModel = model;
return this.transformFilter(filter, (value, key) => {
const field = this.getFieldFromKey(key, parentModel);
if (!field) {
return value;
}
if (field.collection || field.model) {
parentModel = models[field.collection || field.model];
}
return this.processCriteriaMeta(value, key, { convertor });
});
};
},
processCriteriaMeta: function (value, key, { convertor }) {
let type = '=';
if (key.match(/_{1}(?:ne|lte?|gte?|containss?|in)/)) {
type = key.match(/_{1}(?:ne|lte?|gte?|containss?|in)/)[0];
key = key.replace(type, '');
}
return convertor(value, type, key);
},
processGeneratedResults: function() {
return filter => {
if (!_.isArray(filter) && !_.isPlainObject(filter)) {
return filter;
}
return _.transform(filter, (updatedFilter, value, key) => {
// Only set results for object of shape { value, key }
if (_.has(value, 'value') && _.has(value, 'key')) {
const cleanKey = _.replace(value.key, 'where.', '');
_.set(updatedFilter, cleanKey, this.processGeneratedResults()(value.value));
} else {
updatedFilter[key] = this.processGeneratedResults()(value);
}
return updatedFilter;
});
};
},
mergeWhereAndRelationPayloads: function() {
return filter => {
return {
...filter, // Normally here we need to omit where key
relations: {
...filter.where,
relations: filter.relations
}
};
};
return convertParams;
}
};

View File

@ -1,6 +1,6 @@
{
"name": "strapi-utils",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "Shared utilities for the Strapi packages",
"homepage": "http://strapi.io",
"keywords": [
@ -23,7 +23,6 @@
"knex": "^0.13.0",
"lodash": "^4.17.5",
"pino": "^4.7.1",
"pluralize": "^7.0.0",
"shelljs": "^0.7.7"
},
"author": {

View File

@ -12,8 +12,12 @@ const path = require('path');
const fs = require('fs-extra');
const shell = require('shelljs');
// Public
const {cyan} = require('chalk');
const ora = require('ora');
// Logger.
const { cli, logger, packageManager } = require('strapi-utils');
const { cli, packageManager } = require('strapi-utils');
// Local Strapi dependencies.
const packageJSON = require('../package.json');
@ -30,34 +34,31 @@ module.exports = function (plugin, cliArguments) {
const pluginID = `${pluginPrefix}${plugin}`;
const pluginPath = `./plugins/${plugin}`;
let loader = ora(`Install ${cyan(plugin)} plugin`).start();
// Check that we're in a valid Strapi project.
if (!cli.isStrapiApp()) {
return logger.error('This command can only be used inside a Strapi project.');
return loader.fail('This command can only be used inside a Strapi project.');
}
// Check that the plugin is not installed yet.
if (fs.existsSync(pluginPath)) {
logger.error(`It looks like this plugin is already installed. Please check in \`${pluginPath}\`.`);
loader.fail(`It looks like this plugin is already installed. Please check in \`${cyan(pluginPath)}\`.`);
process.exit(1);
}
// Progress message.
logger.debug('Installation in progress...');
if (cliArguments.dev) {
try {
fs.symlinkSync(path.resolve(__dirname, '..', '..', pluginID), path.resolve(process.cwd(), pluginPath), 'dir');
logger.info('The plugin has been successfully installed.');
loader.succeed(`The ${cyan(plugin)} plugin has been successfully installed.`);
process.exit(0);
} catch (e) {
logger.error('An error occurred during plugin installation.');
console.log(e);
loader.fail('An error occurred during plugin installation.');
process.exit(1);
}
} else {
// Debug message.
logger.debug('Installing the plugin from npm registry.');
// Install the plugin from the npm registry.
const isStrapiInstalledWithNPM = packageManager.isStrapiInstalledWithNPM();
@ -72,7 +73,7 @@ module.exports = function (plugin, cliArguments) {
const cmd = isStrapiInstalledWithNPM ? `npm install ${pluginID}@${packageJSON.version} --ignore-scripts --no-save --prefix ${pluginPath}` : `yarn --cwd ${pluginPath} add ${pluginID}@${packageJSON.version} --ignore-scripts --no-save`;
exec(cmd, (err) => {
if (err) {
logger.error(`An error occurred during plugin installation. \nPlease make sure this plugin is available on npm: https://www.npmjs.com/package/${pluginID}`);
loader.fail(`An error occurred during plugin installation. \nPlease make sure this plugin is available on npm: https://www.npmjs.com/package/${pluginID}`);
process.exit(1);
}
@ -81,12 +82,7 @@ module.exports = function (plugin, cliArguments) {
shell.rm('-r', `${pluginPath}/package.json`);
}
// Debug message.
logger.debug('Plugin successfully installed from npm registry.');
try {
// Debug message.
logger.debug(`Moving the \`node_modules/${pluginID}\` folder to the \`./plugins\` folder.`);
// Move the plugin from the `node_modules` folder to the `./plugins` folder.
fs.copySync(`${pluginPath}/node_modules/${pluginID}`, pluginPath, {
overwrite: true,
@ -107,10 +103,10 @@ module.exports = function (plugin, cliArguments) {
}
// Success.
logger.info('The plugin has been successfully installed.');
loader.succeed(`The ${cyan(plugin)} plugin has been successfully installed.`);
process.exit(0);
} catch (err) {
logger.error('An error occurred during plugin installation.');
loader.fail('An error occurred during plugin installation.');
process.exit(1);
}
});

View File

@ -1,6 +1,6 @@
{
"name": "strapi",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.16",
"description": "An open source solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier.",
"homepage": "http://strapi.io",
"keywords": [
@ -32,6 +32,7 @@
"dependencies": {
"async": "^2.1.2",
"boom": "^5.2.0",
"chalk": "^2.4.1",
"cheerio": "^1.0.0-rc.2",
"cross-spawn": "^6.0.5",
"delegates": "^1.0.0",
@ -55,19 +56,20 @@
"node-fetch": "^1.7.3",
"node-schedule": "^1.2.0",
"opn": "^5.3.0",
"ora": "^3.0.0",
"rimraf": "^2.6.2",
"semver": "^5.4.1",
"stack-trace": "0.0.10",
"strapi-generate": "3.0.0-alpha.15",
"strapi-generate-admin": "3.0.0-alpha.15",
"strapi-generate-api": "3.0.0-alpha.15",
"strapi-generate-controller": "3.0.0-alpha.15",
"strapi-generate-model": "3.0.0-alpha.15",
"strapi-generate-new": "3.0.0-alpha.15",
"strapi-generate-plugin": "3.0.0-alpha.15",
"strapi-generate-policy": "3.0.0-alpha.15",
"strapi-generate-service": "3.0.0-alpha.15",
"strapi-utils": "3.0.0-alpha.15"
"strapi-generate": "3.0.0-alpha.16",
"strapi-generate-admin": "3.0.0-alpha.16",
"strapi-generate-api": "3.0.0-alpha.16",
"strapi-generate-controller": "3.0.0-alpha.16",
"strapi-generate-model": "3.0.0-alpha.16",
"strapi-generate-new": "3.0.0-alpha.16",
"strapi-generate-plugin": "3.0.0-alpha.16",
"strapi-generate-policy": "3.0.0-alpha.16",
"strapi-generate-service": "3.0.0-alpha.16",
"strapi-utils": "3.0.0-alpha.16"
},
"author": {
"email": "hi@strapi.io",