Merge branch 'v4/backend' into pluginAPI/loadPlugin

This commit is contained in:
Pierre Noël 2021-08-10 15:33:06 +02:00
commit f4981dd8e7
18 changed files with 117 additions and 375 deletions

View File

@ -15,7 +15,6 @@ const postgres = {
port: 5432,
host: 'localhost',
},
// debug: true,
};
const mysql = {

View File

@ -1,5 +0,0 @@
{
"test-hook": {
"enabled": true
}
}

View File

@ -1,5 +0,0 @@
module.exports = () => {
return {
initialize() {},
};
};

View File

@ -1,5 +0,0 @@
{
"some-hook": {
"enabled": true
}
}

View File

@ -1,5 +0,0 @@
module.exports = () => {
return {
initialize() {},
};
};

View File

@ -110,37 +110,37 @@ const createQueryBuilder = (uid, db) => {
init(params = {}) {
const { _q, where, select, limit, offset, orderBy, groupBy, populate } = params;
if (where) {
if (!_.isNil(where)) {
this.where(where);
}
if (_q) {
if (!_.isNil(_q)) {
this.search(_q);
}
if (select) {
if (!_.isNil(select)) {
this.select(select);
} else {
this.select('*');
}
if (limit) {
if (!_.isNil(limit)) {
this.limit(limit);
}
if (offset) {
if (!_.isNil(offset)) {
this.offset(offset);
}
if (orderBy) {
if (!_.isNil(orderBy)) {
this.orderBy(orderBy);
}
if (groupBy) {
if (!_.isNil(groupBy)) {
this.groupBy(groupBy);
}
if (populate) {
if (!_.isNil(populate)) {
this.populate(populate);
}

View File

@ -1,5 +1,6 @@
'use strict';
const { isUndefined } = require('lodash/fp');
const debug = require('debug')('@strapi/database');
module.exports = db => ({
@ -92,82 +93,7 @@ module.exports = db => ({
// alter table
const schemaBuilder = this.getSchemaBuilder(table, trx);
await schemaBuilder.alterTable(table.name, tableBuilder => {
// Delete indexes / fks / columns
for (const removedIndex of table.indexes.removed) {
debug(`Dropping index ${removedIndex.name}`);
dropIndex(tableBuilder, removedIndex);
}
for (const updateddIndex of table.indexes.updated) {
debug(`Dropping updated index ${updateddIndex.name}`);
dropIndex(tableBuilder, updateddIndex);
}
for (const removedForeignKey of table.foreignKeys.removed) {
debug(`Dropping foreign key ${removedForeignKey.name}`);
dropForeignKey(tableBuilder, removedForeignKey);
}
for (const updatedForeignKey of table.foreignKeys.updated) {
debug(`Dropping updated foreign key ${updatedForeignKey.name}`);
dropForeignKey(tableBuilder, updatedForeignKey);
}
for (const removedColumn of table.columns.removed) {
debug(`Dropping column ${removedColumn.name}`);
dropColumn(tableBuilder, removedColumn);
}
// Update existing columns / foreign keys / indexes
for (const updatedColumn of table.columns.updated) {
debug(`Updating column ${updatedColumn.name}`);
// TODO: cleanup diffs for columns
const { object } = updatedColumn;
/*
type -> recreate the type
args -> recreate the type
unsigned
if changed then recreate the type
if removed then check if old value was true -> recreate the type else do nothing
defaultTo
reapply the default to previous data
notNullable
if null to not null we need a default value to migrate the data
*/
createColumn(tableBuilder, object).alter();
}
for (const updatedForeignKey of table.foreignKeys.updated) {
debug(`Recreating updated foreign key ${updatedForeignKey.name}`);
createForeignKey(tableBuilder, updatedForeignKey);
}
for (const updatedIndex of table.indexes.updated) {
debug(`Recreating updated index ${updatedIndex.name}`);
createIndex(tableBuilder, updatedIndex);
}
for (const addedColumn of table.columns.added) {
debug(`Creating column ${addedColumn.name}`);
createColumn(tableBuilder, addedColumn);
}
for (const addedForeignKey of table.foreignKeys.added) {
debug(`Creating foreign keys ${addedForeignKey.name}`);
createForeignKey(tableBuilder, addedForeignKey);
}
for (const addedIndex of table.indexes.added) {
debug(`Creating index ${addedIndex.name}`);
createIndex(tableBuilder, addedIndex);
}
});
await alterTable(schemaBuilder, table);
}
});
},
@ -262,7 +188,7 @@ const createColumn = (tableBuilder, column) => {
col.unsigned();
}
if (defaultTo) {
if (!isUndefined(defaultTo)) {
// TODO: allow some raw default values
col.defaultTo(...[].concat(defaultTo));
}
@ -292,9 +218,10 @@ const dropColumn = (tableBuilder, column) => {
*/
const createTable = async (schemaBuilder, table) => {
if (await schemaBuilder.hasTable(table.name)) {
throw new Error(`Table already exists ${table.name}`);
debug(`Table ${table.name} already exists trying to alter it`);
// TODO: alter the table instead
// TODO: implement a DB sync at some point
return;
}
await schemaBuilder.createTable(table.name, tableBuilder => {
@ -306,6 +233,85 @@ const createTable = async (schemaBuilder, table) => {
});
};
const alterTable = async (schemaBuilder, table) => {
await schemaBuilder.alterTable(table.name, tableBuilder => {
// Delete indexes / fks / columns
for (const removedIndex of table.indexes.removed) {
debug(`Dropping index ${removedIndex.name}`);
dropIndex(tableBuilder, removedIndex);
}
for (const updateddIndex of table.indexes.updated) {
debug(`Dropping updated index ${updateddIndex.name}`);
dropIndex(tableBuilder, updateddIndex);
}
for (const removedForeignKey of table.foreignKeys.removed) {
debug(`Dropping foreign key ${removedForeignKey.name}`);
dropForeignKey(tableBuilder, removedForeignKey);
}
for (const updatedForeignKey of table.foreignKeys.updated) {
debug(`Dropping updated foreign key ${updatedForeignKey.name}`);
dropForeignKey(tableBuilder, updatedForeignKey);
}
for (const removedColumn of table.columns.removed) {
debug(`Dropping column ${removedColumn.name}`);
dropColumn(tableBuilder, removedColumn);
}
// Update existing columns / foreign keys / indexes
for (const updatedColumn of table.columns.updated) {
debug(`Updating column ${updatedColumn.name}`);
// TODO: cleanup diffs for columns
const { object } = updatedColumn;
/*
type -> recreate the type
args -> recreate the type
unsigned
if changed then recreate the type
if removed then check if old value was true -> recreate the type else do nothing
defaultTo
reapply the default to previous data
notNullable
if null to not null we need a default value to migrate the data
*/
createColumn(tableBuilder, object).alter();
}
for (const updatedForeignKey of table.foreignKeys.updated) {
debug(`Recreating updated foreign key ${updatedForeignKey.name}`);
createForeignKey(tableBuilder, updatedForeignKey);
}
for (const updatedIndex of table.indexes.updated) {
debug(`Recreating updated index ${updatedIndex.name}`);
createIndex(tableBuilder, updatedIndex);
}
for (const addedColumn of table.columns.added) {
debug(`Creating column ${addedColumn.name}`);
createColumn(tableBuilder, addedColumn);
}
for (const addedForeignKey of table.foreignKeys.added) {
debug(`Creating foreign keys ${addedForeignKey.name}`);
createForeignKey(tableBuilder, addedForeignKey);
}
for (const addedIndex of table.indexes.added) {
debug(`Creating index ${addedIndex.name}`);
createIndex(tableBuilder, addedIndex);
}
});
};
/**
* Drops a table from a database
* @param {Knex.SchemaBuilder} schemaBuilder

View File

@ -28,14 +28,9 @@ const createTable = meta => {
columns: [],
};
// TODO: handle indexes
// TODO: handle foreignKeys
for (const key in meta.attributes) {
const attribute = meta.attributes[key];
// TODO: if relation & has a joinColumn -> create it
if (types.isRelation(attribute.type)) {
if (attribute.morphColumn && attribute.owner) {
const { idColumn, typeColumn } = attribute.morphColumn;
@ -43,34 +38,34 @@ const createTable = meta => {
table.columns.push(
createColumn(idColumn.name, {
type: 'integer',
unsigned: true,
column: {
unsigned: true,
},
})
);
table.columns.push(
createColumn(typeColumn.name, {
type: 'string',
})
);
table.columns.push(createColumn(typeColumn.name, { type: 'string' }));
} else if (attribute.joinColumn && attribute.owner) {
// NOTE: we could pass uniquness for oneToOne to avoid creating more than one to one
const { name: columnName, referencedColumn, referencedTable } = attribute.joinColumn;
table.columns.push(
createColumn(columnName, {
type: 'integer',
const column = createColumn(columnName, {
type: 'integer',
column: {
unsigned: true,
})
);
},
});
table.columns.push(column);
table.foreignKeys.push({
// TODO: generate a name
name: `${columnName}_fk`,
name: `${table.name}_${columnName}_fk`,
columns: [columnName],
referencedTable,
referencedColumns: [referencedColumn],
onDelete: 'SET NULL', // NOTE: could allow configuration
// NOTE: could allow configuration
onDelete: 'SET NULL',
});
}
} else if (shouldCreateColumn(attribute)) {

View File

@ -14,7 +14,6 @@ const loadModules = require('./core/loaders/load-modules');
const utils = require('./utils');
const bootstrap = require('./core/loaders/bootstrap');
const initializeMiddlewares = require('./middlewares');
const initializeHooks = require('./hooks');
const createStrapiFs = require('./core/fs');
const createEventHub = require('./services/event-hub');
const createWebhookRunner = require('./services/webhook-runner');
@ -293,9 +292,8 @@ class Strapi {
this.telemetry = createTelemetry(this);
// Initialize hooks and middlewares.
// Initialize middlewares.
await initializeMiddlewares.call(this);
await initializeHooks.call(this);
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);

View File

@ -141,21 +141,6 @@ module.exports = function(strapi) {
return acc;
}, {});
strapi.config.hook.settings = Object.keys(strapi.hook).reduce((acc, current) => {
// Try to find the settings in the current environment, then in the main configurations.
const currentSettings = _.merge(
_.cloneDeep(_.get(strapi.hook[current], ['defaults', current], {})),
strapi.config.get(['hook', 'settings', current], {})
);
acc[current] = !_.isObject(currentSettings) ? {} : currentSettings;
// Ensure that enabled key exist by forcing to false.
_.defaults(acc[current], { enabled: false });
return acc;
}, {});
// default settings
strapi.config.port = strapi.config.get('server.port') || strapi.config.port;
strapi.config.host = strapi.config.get('server.host') || strapi.config.host;

View File

@ -1,116 +0,0 @@
'use strict';
// Dependencies.
const path = require('path');
const fs = require('fs-extra');
const _ = require('lodash');
const glob = require('../../load/glob');
const findPackagePath = require('../../load/package-path');
/**
* Load hooks
*/
module.exports = async function({ installedHooks, /* installedPlugins, */ appPath }) {
let hooks = {};
await Promise.all([
loadHookDependencies(installedHooks, hooks),
// local middleware
loadLocalHooks(appPath, hooks),
// admin hooks
loadAdminHooks(hooks),
// plugins middlewares
// loadPluginsHooks(installedPlugins, hooks),
// local plugin middlewares
loadLocalPluginsHooks(appPath, hooks),
]);
return hooks;
};
const loadHooksInDir = async (dir, hooks) => {
const files = await glob('*/*(index|defaults).*(js|json)', {
cwd: dir,
});
files.forEach(f => {
const name = f.split('/')[0];
mountHooks(name, [path.resolve(dir, f)], hooks);
});
};
const loadLocalHooks = (appPath, hooks) => loadHooksInDir(path.resolve(appPath, 'hooks'), hooks);
// const loadPluginsHooks = async (plugins, hooks) => {
// for (let pluginName of plugins) {
// const dir = path.resolve(findPackagePath(`@strapi/plugin-${pluginName}`), 'hooks');
// await loadHooksInDir(dir, hooks);
// }
// };
const loadAdminHooks = async hooks => {
const hooksDir = 'hooks';
const dir = path.resolve(findPackagePath('@strapi/admin'), hooksDir);
await loadHooksInDir(dir, hooks);
// load ee admin hooks if they exist
if (process.env.STRAPI_DISABLE_EE !== 'true' && strapi.EE) {
await loadHooksInDir(`${dir}/../ee/${hooksDir}`, hooks);
}
};
const loadLocalPluginsHooks = async (appPath, hooks) => {
const pluginsDir = path.resolve(appPath, 'plugins');
if (!fs.existsSync(pluginsDir)) return;
const pluginsNames = await fs.readdir(pluginsDir);
for (let pluginName of pluginsNames) {
// ignore files
const stat = await fs.stat(path.resolve(pluginsDir, pluginName));
if (!stat.isDirectory()) continue;
const dir = path.resolve(pluginsDir, pluginName, 'hooks');
await loadHooksInDir(dir, hooks);
}
};
const loadHookDependencies = async (installedHooks, hooks) => {
for (let hook of installedHooks) {
const hookDir = path.dirname(require.resolve(`@strapi/hook-${hook}`));
const files = await glob('*(index|defaults).*(js|json)', {
cwd: hookDir,
absolute: true,
});
mountHooks(hook, files, hooks);
}
};
const mountHooks = (name, files, hooks) => {
files.forEach(file => {
hooks[name] = hooks[name] || { loaded: false };
let dependencies = [];
try {
dependencies = _.get(require(`@strapi/hook-${name}/package.json`), 'strapi.dependencies', []);
} catch (err) {
// Silent
}
if (_.endsWith(file, 'index.js') && !hooks[name].load) {
Object.defineProperty(hooks[name], 'load', {
configurable: false,
enumerable: true,
get: () => require(file)(strapi),
});
hooks[name].dependencies = dependencies;
return;
}
if (_.endsWith(file, 'defaults.json')) {
hooks[name].defaults = require(file);
return;
}
});
};

View File

@ -16,14 +16,12 @@ const loadApis = require('./load-apis');
const loadMiddlewares = require('./load-middlewares');
const loadExtensions = require('./load-extensions');
const loadHooks = require('./load-hooks');
const loadComponents = require('./load-components');
module.exports = async strapi => {
const [api, middlewares, hook, extensions, components] = await Promise.all([
const [api, middlewares, extensions, components] = await Promise.all([
loadApis(strapi),
loadMiddlewares(strapi),
loadHooks(strapi.config),
loadExtensions(strapi),
loadComponents(strapi),
]);
@ -49,7 +47,6 @@ module.exports = async strapi => {
return {
api,
middlewares,
hook,
extensions,
components,
};

View File

@ -1,97 +0,0 @@
'use strict';
const { uniq, difference, get, isUndefined, merge } = require('lodash');
module.exports = async function() {
/** Utils */
const hookConfig = this.config.hook;
// check if a hook exists
const hookExists = key => {
return !isUndefined(this.hook[key]);
};
// check if a hook is enabled
const hookEnabled = key => get(hookConfig, ['settings', key, 'enabled'], false) === true;
// list of enabled hooks
const enableddHook = Object.keys(this.hook).filter(hookEnabled);
// Method to initialize hooks and emit an event.
const initialize = hookKey => {
if (this.hook[hookKey].loaded === true) return;
const module = this.hook[hookKey].load;
const hookTimeout = get(hookConfig, ['settings', hookKey, 'timeout'], hookConfig.timeout);
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(`(hook: ${hookKey}) is taking too long to load.`),
hookTimeout || 1000
);
this.hook[hookKey] = merge(this.hook[hookKey], module);
Promise.resolve()
.then(() => module.initialize())
.then(() => {
clearTimeout(timeout);
this.hook[hookKey].loaded = true;
resolve();
})
.catch(err => {
clearTimeout(timeout);
if (err) {
return reject(err);
}
});
});
};
/**
* Run init functions
*/
// Run beforeInitialize of every hook
await Promise.all(
enableddHook.map(key => {
const { beforeInitialize } = this.hook[key].load;
if (typeof beforeInitialize === 'function') {
return beforeInitialize();
}
})
);
// run the initialization of an array of hooks sequentially
const initdHookSeq = async hookArr => {
for (let key of uniq(hookArr)) {
await initialize(key);
}
};
const hooksBefore = get(hookConfig, 'load.before', [])
.filter(hookExists)
.filter(hookEnabled);
const hooksAfter = get(hookConfig, 'load.after', [])
.filter(hookExists)
.filter(hookEnabled);
const hooksOrder = get(hookConfig, 'load.order', [])
.filter(hookExists)
.filter(hookEnabled);
const unspecifieddHook = difference(enableddHook, hooksBefore, hooksOrder, hooksAfter);
// before
await initdHookSeq(hooksBefore);
// ordered // rest of hooks
await initdHookSeq(hooksOrder);
await initdHookSeq(unspecifieddHook);
// after
await initdHookSeq(hooksAfter);
};

View File

@ -8,7 +8,6 @@ describe('check-reserved-filename', () => {
['config/functions.json', true],
['config/functions/bootstrapi.js', true],
['config/layout.json', true],
['config/hook.json', true],
['config/middleware.json', true],
// dont match
['config/application.json', false],

View File

@ -3,15 +3,7 @@
const _ = require('lodash');
// files to load with filename key
const prefixedPaths = [
'functions',
'policies',
'locales',
'hook',
'middleware',
'language',
'layout',
];
const prefixedPaths = ['functions', 'policies', 'locales', 'middleware', 'language', 'layout'];
module.exports = function checkReservedFilenames(file) {
return _.some(prefixedPaths, e => file.indexOf(`config/${e}`) >= 0) ? true : false;

View File

@ -294,6 +294,10 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
const entityToUpdate = await db.query(uid).findOne({ where: { id: entityId } });
if (!entityToUpdate) {
return null;
}
const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
const validData = await entityValidator.validateEntityUpdate(model, data, {
@ -334,7 +338,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
});
if (!entityToDelete) {
throw new Error('Entity not found');
return null;
}
await deleteComponents(uid, entityToDelete);

View File

@ -1,6 +1,6 @@
'use strict';
const uploadController = require('../../controllers/Upload');
const uploadController = require('../../controllers/upload');
module.exports = {
upload: uploadController,

View File

@ -1,6 +1,6 @@
'use strict';
const uploadService = require('../../services/Upload');
const uploadService = require('../../services/upload');
const imageManipulation = require('../../services/image-manipulation');
module.exports = {