@akemona-org/strapi 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +163 -0
- package/bin/strapi.js +239 -0
- package/index.d.ts +13 -0
- package/lib/Strapi.js +498 -0
- package/lib/commands/admin-reset.js +51 -0
- package/lib/commands/build.js +47 -0
- package/lib/commands/configurationDump.js +50 -0
- package/lib/commands/configurationRestore.js +160 -0
- package/lib/commands/console.js +26 -0
- package/lib/commands/develop.js +155 -0
- package/lib/commands/generate-template.js +97 -0
- package/lib/commands/generate.js +66 -0
- package/lib/commands/install.js +48 -0
- package/lib/commands/new.js +11 -0
- package/lib/commands/start.js +8 -0
- package/lib/commands/uninstall.js +68 -0
- package/lib/commands/watchAdmin.js +35 -0
- package/lib/core/app-configuration/config-loader.js +56 -0
- package/lib/core/app-configuration/config-provider.js +28 -0
- package/lib/core/app-configuration/index.js +99 -0
- package/lib/core/bootstrap.js +166 -0
- package/lib/core/fs.js +52 -0
- package/lib/core/index.js +21 -0
- package/lib/core/load-admin.js +36 -0
- package/lib/core/load-apis.js +22 -0
- package/lib/core/load-components.js +43 -0
- package/lib/core/load-extensions.js +71 -0
- package/lib/core/load-functions.js +21 -0
- package/lib/core/load-hooks.js +117 -0
- package/lib/core/load-middlewares.js +130 -0
- package/lib/core/load-modules.js +61 -0
- package/lib/core/load-plugins.js +68 -0
- package/lib/core/load-policies.js +36 -0
- package/lib/core/walk.js +27 -0
- package/lib/core-api/controller.js +158 -0
- package/lib/core-api/index.js +33 -0
- package/lib/core-api/service/collection-type.js +122 -0
- package/lib/core-api/service/index.js +81 -0
- package/lib/core-api/service/single-type.js +68 -0
- package/lib/hooks/index.js +97 -0
- package/lib/index.js +3 -0
- package/lib/load/check-reserved-filename.js +18 -0
- package/lib/load/filepath-to-prop-path.js +22 -0
- package/lib/load/glob.js +15 -0
- package/lib/load/index.js +9 -0
- package/lib/load/load-config-files.js +22 -0
- package/lib/load/load-files.js +56 -0
- package/lib/load/package-path.js +9 -0
- package/lib/load/require-file-parse.js +15 -0
- package/lib/middlewares/boom/defaults.json +5 -0
- package/lib/middlewares/boom/index.js +147 -0
- package/lib/middlewares/cors/index.js +66 -0
- package/lib/middlewares/cron/defaults.json +5 -0
- package/lib/middlewares/cron/index.js +43 -0
- package/lib/middlewares/csp/defaults.json +5 -0
- package/lib/middlewares/csp/index.js +26 -0
- package/lib/middlewares/favicon/defaults.json +7 -0
- package/lib/middlewares/favicon/index.js +35 -0
- package/lib/middlewares/gzip/defaults.json +6 -0
- package/lib/middlewares/gzip/index.js +19 -0
- package/lib/middlewares/hsts/defaults.json +7 -0
- package/lib/middlewares/hsts/index.js +30 -0
- package/lib/middlewares/index.js +120 -0
- package/lib/middlewares/ip/defaults.json +7 -0
- package/lib/middlewares/ip/index.js +25 -0
- package/lib/middlewares/language/defaults.json +9 -0
- package/lib/middlewares/language/index.js +40 -0
- package/lib/middlewares/logger/defaults.json +8 -0
- package/lib/middlewares/logger/index.js +63 -0
- package/lib/middlewares/p3p/defaults.json +6 -0
- package/lib/middlewares/p3p/index.js +29 -0
- package/lib/middlewares/parser/defaults.json +10 -0
- package/lib/middlewares/parser/index.js +71 -0
- package/lib/middlewares/poweredBy/defaults.json +5 -0
- package/lib/middlewares/poweredBy/index.js +16 -0
- package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
- package/lib/middlewares/public/assets/images/logo_login.png +0 -0
- package/lib/middlewares/public/defaults.json +8 -0
- package/lib/middlewares/public/index.html +66 -0
- package/lib/middlewares/public/index.js +98 -0
- package/lib/middlewares/public/serve-static.js +23 -0
- package/lib/middlewares/responseTime/defaults.json +5 -0
- package/lib/middlewares/responseTime/index.js +25 -0
- package/lib/middlewares/responses/defaults.json +5 -0
- package/lib/middlewares/responses/index.js +18 -0
- package/lib/middlewares/router/defaults.json +7 -0
- package/lib/middlewares/router/index.js +64 -0
- package/lib/middlewares/router/utils/composeEndpoint.js +25 -0
- package/lib/middlewares/router/utils/routerChecker.js +92 -0
- package/lib/middlewares/session/defaults.json +18 -0
- package/lib/middlewares/session/index.js +140 -0
- package/lib/middlewares/xframe/defaults.json +6 -0
- package/lib/middlewares/xframe/index.js +33 -0
- package/lib/middlewares/xss/defaults.json +6 -0
- package/lib/middlewares/xss/index.js +30 -0
- package/lib/services/core-store.js +144 -0
- package/lib/services/entity-service.js +260 -0
- package/lib/services/entity-validator/index.js +199 -0
- package/lib/services/entity-validator/validators.js +125 -0
- package/lib/services/event-hub.js +15 -0
- package/lib/services/metrics/index.js +103 -0
- package/lib/services/metrics/is-truthy.js +9 -0
- package/lib/services/metrics/middleware.js +33 -0
- package/lib/services/metrics/rate-limiter.js +27 -0
- package/lib/services/metrics/sender.js +76 -0
- package/lib/services/metrics/stringify-deep.js +22 -0
- package/lib/services/utils/upload-files.js +70 -0
- package/lib/services/webhook-runner.js +159 -0
- package/lib/services/webhook-store.js +97 -0
- package/lib/services/worker-queue.js +58 -0
- package/lib/utils/addSlash.js +10 -0
- package/lib/utils/ee.js +123 -0
- package/lib/utils/get-prefixed-dependencies.js +7 -0
- package/lib/utils/index.js +25 -0
- package/lib/utils/openBrowser.js +145 -0
- package/lib/utils/resources/key.pub +9 -0
- package/lib/utils/resources/openChrome.applescript +83 -0
- package/lib/utils/run-checks.js +37 -0
- package/lib/utils/success.js +31 -0
- package/lib/utils/update-notifier/index.js +96 -0
- package/lib/utils/url-from-segments.js +13 -0
- package/package.json +143 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const convert = require('koa-convert');
|
|
4
|
+
const { xssProtection } = require('koa-lusca');
|
|
5
|
+
|
|
6
|
+
module.exports = strapi => {
|
|
7
|
+
return {
|
|
8
|
+
initialize() {
|
|
9
|
+
const defaults = require('./defaults.json');
|
|
10
|
+
|
|
11
|
+
strapi.app.use(async (ctx, next) => {
|
|
12
|
+
if (ctx.request.admin) {
|
|
13
|
+
return await convert(
|
|
14
|
+
xssProtection({
|
|
15
|
+
enabled: true,
|
|
16
|
+
mode: defaults.xss.mode,
|
|
17
|
+
})
|
|
18
|
+
)(ctx, next);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const xssConfig = strapi.config.get('middleware.settings.xss');
|
|
22
|
+
if (xssConfig.enabled) {
|
|
23
|
+
return await convert(xssProtection(xssConfig))(ctx, next);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await next();
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const coreStoreModel = config => ({
|
|
4
|
+
connection: config.get('database.defaultConnection'),
|
|
5
|
+
uid: 'strapi::core-store',
|
|
6
|
+
info: {
|
|
7
|
+
name: 'core_store',
|
|
8
|
+
description: '',
|
|
9
|
+
},
|
|
10
|
+
pluginOptions: {
|
|
11
|
+
'content-manager': {
|
|
12
|
+
visible: false,
|
|
13
|
+
},
|
|
14
|
+
'content-type-builder': {
|
|
15
|
+
visible: false,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
attributes: {
|
|
19
|
+
key: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
},
|
|
22
|
+
value: {
|
|
23
|
+
type: 'text',
|
|
24
|
+
},
|
|
25
|
+
type: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
},
|
|
28
|
+
environment: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
},
|
|
31
|
+
tag: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
globalId: 'StrapiConfigs',
|
|
36
|
+
collectionName: 'core_store',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const createCoreStore = ({ environment: defaultEnv, db }) => {
|
|
40
|
+
return (source = {}) => {
|
|
41
|
+
async function get(params = {}) {
|
|
42
|
+
const { key, environment = defaultEnv, type = 'core', name = '', tag = '' } = Object.assign(
|
|
43
|
+
{},
|
|
44
|
+
source,
|
|
45
|
+
params
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const prefix = `${type}${name ? `_${name}` : ''}`;
|
|
49
|
+
|
|
50
|
+
const where = {
|
|
51
|
+
key: `${prefix}_${key}`,
|
|
52
|
+
environment,
|
|
53
|
+
tag,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const data = await db.query('core_store').findOne(where);
|
|
57
|
+
|
|
58
|
+
if (!data) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
data.type === 'object' ||
|
|
64
|
+
data.type === 'array' ||
|
|
65
|
+
data.type === 'boolean' ||
|
|
66
|
+
data.type === 'string'
|
|
67
|
+
) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(data.value);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return new Date(data.value);
|
|
72
|
+
}
|
|
73
|
+
} else if (data.type === 'number') {
|
|
74
|
+
return parseFloat(data.value);
|
|
75
|
+
} else {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function set(params = {}) {
|
|
81
|
+
const { key, value, environment = defaultEnv, type, name, tag = '' } = Object.assign(
|
|
82
|
+
{},
|
|
83
|
+
source,
|
|
84
|
+
params
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const prefix = `${type}${name ? `_${name}` : ''}`;
|
|
88
|
+
|
|
89
|
+
const where = {
|
|
90
|
+
key: `${prefix}_${key}`,
|
|
91
|
+
environment,
|
|
92
|
+
tag,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const data = await db.query('core_store').findOne(where);
|
|
96
|
+
|
|
97
|
+
if (data) {
|
|
98
|
+
Object.assign(data, {
|
|
99
|
+
value: JSON.stringify(value) || value.toString(),
|
|
100
|
+
type: (typeof value).toString(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await db.query('core_store').update({ id: data.id }, data);
|
|
104
|
+
} else {
|
|
105
|
+
const data = Object.assign({}, where, {
|
|
106
|
+
value: JSON.stringify(value) || value.toString(),
|
|
107
|
+
type: (typeof value).toString(),
|
|
108
|
+
tag,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await db.query('core_store').create(data);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function deleteFn(params = {}) {
|
|
116
|
+
const { key, environment = defaultEnv, type, name, tag = '' } = Object.assign(
|
|
117
|
+
{},
|
|
118
|
+
source,
|
|
119
|
+
params
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const prefix = `${type}${name ? `_${name}` : ''}`;
|
|
123
|
+
|
|
124
|
+
const where = {
|
|
125
|
+
key: `${prefix}_${key}`,
|
|
126
|
+
environment,
|
|
127
|
+
tag,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
await db.query('core_store').delete(where);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
get,
|
|
135
|
+
set,
|
|
136
|
+
delete: deleteFn,
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
coreStoreModel,
|
|
143
|
+
createCoreStore,
|
|
144
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const delegate = require('delegates');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
sanitizeEntity,
|
|
8
|
+
webhook: webhookUtils,
|
|
9
|
+
contentTypes: contentTypesUtils,
|
|
10
|
+
} = require('@akemona-org/strapi-utils');
|
|
11
|
+
const uploadFiles = require('./utils/upload-files');
|
|
12
|
+
|
|
13
|
+
// TODO: those should be strapi events used by the webhooks not the other way arround
|
|
14
|
+
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
|
15
|
+
|
|
16
|
+
module.exports = (ctx) => {
|
|
17
|
+
const implementation = createDefaultImplementation(ctx);
|
|
18
|
+
|
|
19
|
+
const service = {
|
|
20
|
+
implementation,
|
|
21
|
+
decorate(decorator) {
|
|
22
|
+
if (typeof decorator !== 'function') {
|
|
23
|
+
throw new Error(`Decorator must be a function, received ${typeof decorator}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.implementation = Object.assign({}, this.implementation, decorator(this.implementation));
|
|
27
|
+
return this;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const delegator = delegate(service, 'implementation');
|
|
32
|
+
|
|
33
|
+
// delegate every method in implementation
|
|
34
|
+
Object.keys(service.implementation).forEach((key) => delegator.method(key));
|
|
35
|
+
|
|
36
|
+
return service;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
|
40
|
+
/**
|
|
41
|
+
* expose some utils so the end users can use them
|
|
42
|
+
*/
|
|
43
|
+
uploadFiles,
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns default opt
|
|
47
|
+
* it is async so decorators can do async processing
|
|
48
|
+
* @param {object} params - query params to extend
|
|
49
|
+
* @param {object=} ctx - Query context
|
|
50
|
+
* @param {object} ctx.model - Model that is being used
|
|
51
|
+
*/
|
|
52
|
+
async wrapOptions(options = {}) {
|
|
53
|
+
return options;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a list of entries
|
|
58
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
59
|
+
* @param {object} ctx - Query context
|
|
60
|
+
* @param {object} ctx.model - Model that is being used
|
|
61
|
+
*/
|
|
62
|
+
async find(opts, { model }) {
|
|
63
|
+
const { params, populate } = await this.wrapOptions(opts, { model, action: 'find' });
|
|
64
|
+
|
|
65
|
+
const { kind } = db.getModel(model);
|
|
66
|
+
|
|
67
|
+
// return first element and ignore filters
|
|
68
|
+
if (kind === 'singleType') {
|
|
69
|
+
const results = await db.query(model).find({ ...params, _limit: 1 }, populate);
|
|
70
|
+
return _.first(results) || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return db.query(model).find(params, populate);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns a paginated list of entries
|
|
78
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
79
|
+
* @param {object} ctx - Query context
|
|
80
|
+
* @param {object} ctx.model - Model that is being used
|
|
81
|
+
*/
|
|
82
|
+
async findPage(opts, { model }) {
|
|
83
|
+
const { params, populate } = await this.wrapOptions(opts, { model, action: 'findPage' });
|
|
84
|
+
|
|
85
|
+
return db.query(model).findPage(params, populate);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns a list of entries with relation counters
|
|
90
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
91
|
+
* @param {object} ctx - Query context
|
|
92
|
+
* @param {object} ctx.model - Model that is being used
|
|
93
|
+
*/
|
|
94
|
+
async findWithRelationCounts(opts, { model }) {
|
|
95
|
+
const { params, populate } = await this.wrapOptions(opts, {
|
|
96
|
+
model,
|
|
97
|
+
action: 'findWithRelationCounts',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return db.query(model).findWithRelationCounts(params, populate);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns one entry
|
|
105
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
106
|
+
* @param {object} ctx - Query context
|
|
107
|
+
* @param {object} ctx.model - Model that is being used
|
|
108
|
+
*/
|
|
109
|
+
async findOne(opts, { model }) {
|
|
110
|
+
const { params, populate } = await this.wrapOptions(opts, { model, action: 'findOne' });
|
|
111
|
+
|
|
112
|
+
return db.query(model).findOne(params, populate);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns a count of entries
|
|
117
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
118
|
+
* @param {object} ctx - Query context
|
|
119
|
+
* @param {object} ctx.model - Model that is being used
|
|
120
|
+
*/
|
|
121
|
+
async count(opts, { model }) {
|
|
122
|
+
const { params } = await this.wrapOptions(opts, { model, action: 'count' });
|
|
123
|
+
|
|
124
|
+
return db.query(model).count(params);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates & returns a new entry
|
|
129
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
130
|
+
* @param {object} ctx - Query context
|
|
131
|
+
* @param {object} ctx.model - Model that is being used
|
|
132
|
+
*/
|
|
133
|
+
async create(opts, { model }) {
|
|
134
|
+
const { data, files } = await this.wrapOptions(opts, { model, action: 'create' });
|
|
135
|
+
|
|
136
|
+
const modelDef = db.getModel(model);
|
|
137
|
+
|
|
138
|
+
const isDraft = contentTypesUtils.isDraft(data, modelDef);
|
|
139
|
+
|
|
140
|
+
const validData = await entityValidator.validateEntityCreation(modelDef, data, { isDraft });
|
|
141
|
+
|
|
142
|
+
let entry = await db.query(model).create(validData);
|
|
143
|
+
|
|
144
|
+
if (files && Object.keys(files).length > 0) {
|
|
145
|
+
await this.uploadFiles(entry, files, { model });
|
|
146
|
+
entry = await this.findOne({ params: { id: entry.id } }, { model });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
eventHub.emit(ENTRY_CREATE, {
|
|
150
|
+
model: modelDef.modelName,
|
|
151
|
+
entry: sanitizeEntity(entry, { model: modelDef }),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return entry;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Updates & returns an existing entry
|
|
159
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
160
|
+
* @param {object} ctx - Query context
|
|
161
|
+
* @param {object} ctx.model - Model that is being used
|
|
162
|
+
*/
|
|
163
|
+
async update(opts, { model }) {
|
|
164
|
+
const { params, data, files } = await this.wrapOptions(opts, { model, action: 'update' });
|
|
165
|
+
|
|
166
|
+
const modelDef = db.getModel(model);
|
|
167
|
+
const existingEntry = await db.query(model).findOne(params);
|
|
168
|
+
|
|
169
|
+
const isDraft = contentTypesUtils.isDraft(existingEntry, modelDef);
|
|
170
|
+
|
|
171
|
+
const validData = await entityValidator.validateEntityUpdate(modelDef, data, {
|
|
172
|
+
isDraft,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
let entry = await db.query(model).update(params, validData);
|
|
176
|
+
|
|
177
|
+
if (files && Object.keys(files).length > 0) {
|
|
178
|
+
await this.uploadFiles(entry, files, { model });
|
|
179
|
+
entry = await this.findOne({ params: { id: entry.id } }, { model });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
eventHub.emit(ENTRY_UPDATE, {
|
|
183
|
+
model: modelDef.modelName,
|
|
184
|
+
entry: sanitizeEntity(entry, { model: modelDef }),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return entry;
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Deletes & returns the entry that was deleted
|
|
192
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
193
|
+
* @param {object} ctx - Query context
|
|
194
|
+
* @param {object} ctx.model - Model that is being used
|
|
195
|
+
*/
|
|
196
|
+
async delete(opts, { model }) {
|
|
197
|
+
const { params } = await this.wrapOptions(opts, { model, action: 'delete' });
|
|
198
|
+
|
|
199
|
+
const entry = await db.query(model).delete(params);
|
|
200
|
+
|
|
201
|
+
const modelDef = db.getModel(model);
|
|
202
|
+
eventHub.emit(ENTRY_DELETE, {
|
|
203
|
+
model: modelDef.modelName,
|
|
204
|
+
entry: sanitizeEntity(entry, { model: modelDef }),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return entry;
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Returns a list of matching entries
|
|
212
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
213
|
+
* @param {object} ctx - Query context
|
|
214
|
+
* @param {object} ctx.model - Model that is being used
|
|
215
|
+
*/
|
|
216
|
+
async search(opts, { model }) {
|
|
217
|
+
const { params, populate } = await this.wrapOptions(opts, { model, action: 'search' });
|
|
218
|
+
|
|
219
|
+
return db.query(model).search(params, populate);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns a list of matching entries with relations counters
|
|
224
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
225
|
+
* @param {object} ctx - Query context
|
|
226
|
+
* @param {object} ctx.model - Model that is being used
|
|
227
|
+
*/
|
|
228
|
+
async searchWithRelationCounts(opts, { model }) {
|
|
229
|
+
const { params, populate } = await this.wrapOptions(opts, {
|
|
230
|
+
model,
|
|
231
|
+
action: 'searchWithRelationCounts',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return db.query(model).searchWithRelationCounts(params, populate);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns a paginated list of matching entries
|
|
239
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
240
|
+
* @param {object} ctx - Query context
|
|
241
|
+
* @param {object} ctx.model - Model that is being used
|
|
242
|
+
*/
|
|
243
|
+
async searchPage(opts, { model }) {
|
|
244
|
+
const { params, populate } = await this.wrapOptions(opts, { model, action: 'searchPage' });
|
|
245
|
+
|
|
246
|
+
return db.query(model).searchPage(params, populate);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Promise to count searched records
|
|
251
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
252
|
+
* @param {object} ctx - Query context
|
|
253
|
+
* @param {object} ctx.model - Model that is being used
|
|
254
|
+
*/
|
|
255
|
+
async countSearch(opts, { model }) {
|
|
256
|
+
const { params } = await this.wrapOptions(opts, { model, action: 'countSearch' });
|
|
257
|
+
|
|
258
|
+
return db.query(model).countSearch(params);
|
|
259
|
+
},
|
|
260
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity validator
|
|
3
|
+
* Module that will validate input data for entity creation or edition
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { has, assoc, prop } = require('lodash/fp');
|
|
8
|
+
const strapiUtils = require('@akemona-org/strapi-utils');
|
|
9
|
+
const validators = require('./validators');
|
|
10
|
+
|
|
11
|
+
const { yup, formatYupErrors } = strapiUtils;
|
|
12
|
+
const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
|
|
13
|
+
|
|
14
|
+
const addMinMax = (attr, validator, data) => {
|
|
15
|
+
if (Number.isInteger(attr.min) && (attr.required || (Array.isArray(data) && data.length > 0))) {
|
|
16
|
+
validator = validator.min(attr.min);
|
|
17
|
+
}
|
|
18
|
+
if (Number.isInteger(attr.max)) {
|
|
19
|
+
validator = validator.max(attr.max);
|
|
20
|
+
}
|
|
21
|
+
return validator;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const addRequiredValidation = (createOrUpdate) => (required, validator) => {
|
|
25
|
+
if (required) {
|
|
26
|
+
if (createOrUpdate === 'creation') {
|
|
27
|
+
validator = validator.notNil();
|
|
28
|
+
} else if (createOrUpdate === 'update') {
|
|
29
|
+
validator = validator.notNull();
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
validator = validator.nullable();
|
|
33
|
+
}
|
|
34
|
+
return validator;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const addDefault = (createOrUpdate) => (attr, validator) => {
|
|
38
|
+
if (createOrUpdate === 'creation') {
|
|
39
|
+
if (
|
|
40
|
+
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
|
41
|
+
!attr.required
|
|
42
|
+
) {
|
|
43
|
+
validator = validator.default([]);
|
|
44
|
+
} else {
|
|
45
|
+
validator = validator.default(attr.default);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
validator = validator.default(undefined);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return validator;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const preventCast = (validator) => validator.transform((val, originalVal) => originalVal);
|
|
55
|
+
|
|
56
|
+
const createComponentValidator =
|
|
57
|
+
(createOrUpdate) =>
|
|
58
|
+
(attr, data, { isDraft }) => {
|
|
59
|
+
let validator;
|
|
60
|
+
|
|
61
|
+
const [model] = strapi.db.getModelsByAttribute(attr);
|
|
62
|
+
if (!model) {
|
|
63
|
+
throw new Error('Validation failed: Model not found');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (prop('repeatable', attr) === true) {
|
|
67
|
+
validator = yup
|
|
68
|
+
.array()
|
|
69
|
+
.of(
|
|
70
|
+
yup.lazy((item) =>
|
|
71
|
+
createModelValidator(createOrUpdate)(model, item, { isDraft }).notNull()
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
validator = addRequiredValidation(createOrUpdate)(true, validator);
|
|
75
|
+
validator = addMinMax(attr, validator, data);
|
|
76
|
+
} else {
|
|
77
|
+
validator = createModelValidator(createOrUpdate)(model, data, { isDraft });
|
|
78
|
+
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return validator;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const createDzValidator =
|
|
85
|
+
(createOrUpdate) =>
|
|
86
|
+
(attr, data, { isDraft }) => {
|
|
87
|
+
let validator;
|
|
88
|
+
|
|
89
|
+
validator = yup.array().of(
|
|
90
|
+
yup.lazy((item) => {
|
|
91
|
+
const model = strapi.getModel(prop('__component', item));
|
|
92
|
+
const schema = yup
|
|
93
|
+
.object()
|
|
94
|
+
.shape({
|
|
95
|
+
__component: yup.string().required().oneOf(Object.keys(strapi.components)),
|
|
96
|
+
})
|
|
97
|
+
.notNull();
|
|
98
|
+
|
|
99
|
+
return model
|
|
100
|
+
? schema.concat(createModelValidator(createOrUpdate)(model, item, { isDraft }))
|
|
101
|
+
: schema;
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
validator = addRequiredValidation(createOrUpdate)(true, validator);
|
|
105
|
+
validator = addMinMax(attr, validator, data);
|
|
106
|
+
|
|
107
|
+
return validator;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const createRelationValidator =
|
|
111
|
+
(createOrUpdate) =>
|
|
112
|
+
(attr, data, { isDraft }) => {
|
|
113
|
+
let validator;
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(data)) {
|
|
116
|
+
validator = yup.array().of(yup.mixed());
|
|
117
|
+
} else {
|
|
118
|
+
validator = yup.mixed();
|
|
119
|
+
}
|
|
120
|
+
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
|
121
|
+
|
|
122
|
+
return validator;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const createScalarAttributeValidator =
|
|
126
|
+
(createOrUpdate) =>
|
|
127
|
+
(attr, { isDraft }) => {
|
|
128
|
+
let validator;
|
|
129
|
+
|
|
130
|
+
if (has(attr.type, validators)) {
|
|
131
|
+
validator = validators[attr.type](attr, { isDraft });
|
|
132
|
+
} else {
|
|
133
|
+
// No validators specified - fall back to mixed
|
|
134
|
+
validator = yup.mixed();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
|
|
138
|
+
|
|
139
|
+
return validator;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const createAttributeValidator =
|
|
143
|
+
(createOrUpdate) =>
|
|
144
|
+
(attr, data, { isDraft }) => {
|
|
145
|
+
let validator;
|
|
146
|
+
if (isMediaAttribute(attr)) {
|
|
147
|
+
validator = yup.mixed();
|
|
148
|
+
} else if (isScalarAttribute(attr)) {
|
|
149
|
+
validator = createScalarAttributeValidator(createOrUpdate)(attr, { isDraft });
|
|
150
|
+
} else {
|
|
151
|
+
if (attr.type === 'component') {
|
|
152
|
+
validator = createComponentValidator(createOrUpdate)(attr, data, { isDraft });
|
|
153
|
+
} else if (attr.type === 'dynamiczone') {
|
|
154
|
+
validator = createDzValidator(createOrUpdate)(attr, data, { isDraft });
|
|
155
|
+
} else {
|
|
156
|
+
validator = createRelationValidator(createOrUpdate)(attr, data, { isDraft });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
validator = preventCast(validator);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
validator = addDefault(createOrUpdate)(attr, validator);
|
|
163
|
+
|
|
164
|
+
return validator;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const createModelValidator =
|
|
168
|
+
(createOrUpdate) =>
|
|
169
|
+
(model, data, { isDraft }) => {
|
|
170
|
+
const writableAttributes = model ? getWritableAttributes(model) : [];
|
|
171
|
+
|
|
172
|
+
const schema = writableAttributes.reduce((validators, attributeName) => {
|
|
173
|
+
const validator = createAttributeValidator(createOrUpdate)(
|
|
174
|
+
model.attributes[attributeName],
|
|
175
|
+
prop(attributeName, data),
|
|
176
|
+
{ isDraft }
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return assoc(attributeName, validator)(validators);
|
|
180
|
+
}, {});
|
|
181
|
+
|
|
182
|
+
return yup.object().shape(schema);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const createValidateEntity =
|
|
186
|
+
(createOrUpdate) =>
|
|
187
|
+
async (model, data, { isDraft = false } = {}) => {
|
|
188
|
+
try {
|
|
189
|
+
const validator = createModelValidator(createOrUpdate)(model, data, { isDraft }).required();
|
|
190
|
+
return await validator.validate(data, { abortEarly: false });
|
|
191
|
+
} catch (e) {
|
|
192
|
+
throw strapi.errors.badRequest('ValidationError', { errors: formatYupErrors(e) });
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
validateEntityCreation: createValidateEntity('creation'),
|
|
198
|
+
validateEntityUpdate: createValidateEntity('update'),
|
|
199
|
+
};
|