mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Init fields
This commit is contained in:
parent
d33a363c88
commit
a1e2fc6829
@ -3,24 +3,24 @@ const sqlite = {
|
||||
connection: {
|
||||
filename: '.tmp/data.db',
|
||||
},
|
||||
// debug: true,
|
||||
useNullAsDefault: true,
|
||||
};
|
||||
|
||||
const postgres = {
|
||||
client: 'postgres',
|
||||
connection: {
|
||||
client: 'postgres',
|
||||
database: 'strapi',
|
||||
username: 'strapi',
|
||||
password: 'strapi',
|
||||
port: 5432,
|
||||
host: 'localhost',
|
||||
},
|
||||
debug: true,
|
||||
};
|
||||
|
||||
const mysql = {
|
||||
client: 'mysql',
|
||||
connection: {
|
||||
client: 'mysql',
|
||||
database: 'strapi',
|
||||
username: 'strapi',
|
||||
password: 'strapi',
|
||||
|
||||
@ -47,7 +47,7 @@ module.exports = async () => {
|
||||
|
||||
await roleService.createRolesIfNoneExist();
|
||||
|
||||
// await roleService.resetSuperAdminPermissions();
|
||||
await roleService.resetSuperAdminPermissions();
|
||||
await roleService.displayWarningIfNoSuperAdmin();
|
||||
|
||||
// await permissionService.ensureBoundPermissionsInDatabase();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
CONTENT_TYPE_SECTION: 'contentTypes',
|
||||
SUPER_ADMIN_CODE: 'strapi-super-admin',
|
||||
EDITOR_CODE: 'strapi-editor',
|
||||
AUTHOR_CODE: 'strapi-author',
|
||||
|
||||
@ -26,13 +26,11 @@ const permissionDomain = require('../../domain/permission/index');
|
||||
* @returns {Promise<array>}
|
||||
*/
|
||||
const deleteByRolesIds = async rolesIds => {
|
||||
const deletedPermissions = await strapi.query('strapi::permission').delete({
|
||||
await strapi.query('strapi::permission').deleteMany({
|
||||
where: {
|
||||
role: { id: rolesIds },
|
||||
},
|
||||
});
|
||||
|
||||
return permissionDomain.toPermission(deletedPermissions);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -41,11 +39,14 @@ const deleteByRolesIds = async rolesIds => {
|
||||
* @returns {Promise<array>}
|
||||
*/
|
||||
const deleteByIds = async ids => {
|
||||
const deletedPermissions = await strapi
|
||||
.query('strapi::permission')
|
||||
.delete({ where: { id: ids } });
|
||||
|
||||
return permissionDomain.toPermission(deletedPermissions);
|
||||
await Promise.all(
|
||||
ids.map(id => {
|
||||
return strapi.query('strapi::permission').delete({ where: { id } });
|
||||
})
|
||||
);
|
||||
|
||||
// TODO: find a way to do delete many with auto association deletes (FKs should do the job)
|
||||
// await strapi.query('strapi::permission').deleteMany({ where: { id: ids } });
|
||||
};
|
||||
|
||||
/**
|
||||
@ -54,9 +55,11 @@ const deleteByIds = async ids => {
|
||||
* @returns {Promise<*[]|*>}
|
||||
*/
|
||||
const createMany = async permissions => {
|
||||
const createdPermissions = await strapi
|
||||
.query('strapi::permission')
|
||||
.createMany({ data: permissions });
|
||||
const createdPermissions = await Promise.all(
|
||||
permissions.map(permission => {
|
||||
return strapi.query('strapi::permission').create({ data: permission });
|
||||
})
|
||||
);
|
||||
|
||||
return permissionDomain.toPermission(createdPermissions);
|
||||
};
|
||||
@ -68,11 +71,11 @@ const createMany = async permissions => {
|
||||
* @param attributes
|
||||
*/
|
||||
const update = async (params, attributes) => {
|
||||
const updatedPermissions = await strapi
|
||||
const updatedPermission = await strapi
|
||||
.query('strapi::permission')
|
||||
.update({ where: params, data: attributes });
|
||||
|
||||
return permissionDomain.toPermission(updatedPermissions);
|
||||
return permissionDomain.toPermission(updatedPermission);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -80,7 +83,7 @@ const update = async (params, attributes) => {
|
||||
* @param params query params to find the permissions
|
||||
* @returns {Promise<Permission[]>}
|
||||
*/
|
||||
const find = async (params = {}) => {
|
||||
const findMany = async (params = {}) => {
|
||||
const rawPermissions = await strapi.query('strapi::permission').findMany(params);
|
||||
|
||||
return permissionDomain.toPermission(rawPermissions);
|
||||
@ -88,15 +91,11 @@ const find = async (params = {}) => {
|
||||
|
||||
/**
|
||||
* Find all permissions for a user
|
||||
* @param roles
|
||||
* @param user - user
|
||||
* @returns {Promise<Permission[]>}
|
||||
*/
|
||||
const findUserPermissions = async ({ roles }) => {
|
||||
if (!isArray(roles)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return find({ role: { id: roles.map(prop('id')) } });
|
||||
const findUserPermissions = async user => {
|
||||
return findMany({ where: { role: { users: { id: user.id } } } });
|
||||
};
|
||||
|
||||
const filterPermissionsToRemove = async permissions => {
|
||||
@ -208,10 +207,14 @@ const ensureBoundPermissionsInDatabase = async () => {
|
||||
for (const contentType of contentTypes) {
|
||||
const boundActions = getBoundActionsBySubject(editorRole, contentType.uid);
|
||||
|
||||
const permissions = await find({
|
||||
subject: contentType.uid,
|
||||
action_in: boundActions,
|
||||
role: editorRole.id,
|
||||
const permissions = await findMany({
|
||||
where: {
|
||||
subject: contentType.uid,
|
||||
action: boundActions,
|
||||
role: {
|
||||
id: editorRole.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (permissions.length === 0) {
|
||||
@ -248,7 +251,7 @@ const ensureBoundPermissionsInDatabase = async () => {
|
||||
|
||||
module.exports = {
|
||||
createMany,
|
||||
find,
|
||||
findMany,
|
||||
deleteByRolesIds,
|
||||
deleteByIds,
|
||||
findUserPermissions,
|
||||
|
||||
1
packages/core/admin/services/role.d.ts
vendored
Normal file
1
packages/core/admin/services/role.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from './role';
|
||||
@ -21,7 +21,7 @@ const {
|
||||
const permissionDomain = require('../domain/permission');
|
||||
const { validatePermissionsExist } = require('../validation/permission');
|
||||
const { getService } = require('../utils');
|
||||
const { SUPER_ADMIN_CODE } = require('./constants');
|
||||
const { SUPER_ADMIN_CODE, CONTENT_TYPE_SECTION } = require('./constants');
|
||||
|
||||
const hooks = {
|
||||
willResetSuperAdminPermissions: createAsyncSeriesWaterfallHook(),
|
||||
@ -346,7 +346,7 @@ const assignPermissions = async (roleId, permissions = []) => {
|
||||
// Transform each permission into a Permission instance
|
||||
.map(permissionDomain.create);
|
||||
|
||||
const existingPermissions = await getService('permission').find({
|
||||
const existingPermissions = await getService('permission').findMany({
|
||||
where: { role: { id: roleId } },
|
||||
populate: ['role'],
|
||||
});
|
||||
@ -356,20 +356,29 @@ const assignPermissions = async (roleId, permissions = []) => {
|
||||
permissionsWithRole,
|
||||
existingPermissions
|
||||
);
|
||||
|
||||
const permissionsToDelete = differenceWith(
|
||||
arePermissionsEqual,
|
||||
existingPermissions,
|
||||
permissionsWithRole
|
||||
);
|
||||
|
||||
const permissionsToReturn = differenceBy('id', permissionsToDelete, existingPermissions);
|
||||
|
||||
console.log(
|
||||
existingPermissions.length,
|
||||
permissionsToAdd.length,
|
||||
permissionsToDelete.length,
|
||||
permissionsToReturn
|
||||
);
|
||||
|
||||
if (permissionsToDelete.length > 0) {
|
||||
await getService('permission').deleteByIds(permissionsToDelete.map(prop('id')));
|
||||
}
|
||||
|
||||
if (permissionsToAdd.length > 0) {
|
||||
const createdPermissions = await addPermissions(roleId, permissionsToAdd);
|
||||
permissionsToReturn.push(...createdPermissions.map(p => ({ ...p, role: p.role.id })));
|
||||
await addPermissions(roleId, permissionsToAdd);
|
||||
permissionsToReturn.push(...permissionsToAdd);
|
||||
}
|
||||
|
||||
if (!isSuperAdmin && (permissionsToAdd.length || permissionsToDelete.length)) {
|
||||
@ -390,6 +399,8 @@ const addPermissions = async (roleId, permissions) => {
|
||||
return createMany(permissionsWithRole);
|
||||
};
|
||||
|
||||
const isContentTypeAction = action => action.section === CONTENT_TYPE_SECTION;
|
||||
|
||||
/**
|
||||
* Reset super admin permissions (giving it all permissions)
|
||||
* @returns {Promise<>}
|
||||
@ -401,8 +412,9 @@ const resetSuperAdminPermissions = async () => {
|
||||
}
|
||||
|
||||
const allActions = getService('permission').actionProvider.values();
|
||||
const contentTypesActions = allActions.filter(a => a.section === 'contentTypes');
|
||||
const otherActions = allActions.filter(a => a.section !== 'contentTypes');
|
||||
|
||||
const contentTypesActions = allActions.filter(action => isContentTypeAction(action));
|
||||
const otherActions = allActions.filter(action => !isContentTypeAction(action));
|
||||
|
||||
// First, get the content-types permissions
|
||||
const permissions = getService('content-type').getPermissionsWithNestedFields(
|
||||
|
||||
@ -286,12 +286,14 @@ const assignARoleToAll = async roleId => {
|
||||
},
|
||||
});
|
||||
|
||||
await strapi.query('strapi::role').update({
|
||||
where: { id: roleId },
|
||||
data: {
|
||||
users: users.map(u => u.id),
|
||||
},
|
||||
});
|
||||
await Promise.all(
|
||||
users.map(user => {
|
||||
return strapi.query('strapi::user').update({
|
||||
where: { id: user.id },
|
||||
data: { roles: [roleId] },
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/** Display a warning if some users don't have at least one role
|
||||
|
||||
15
packages/core/admin/utils/index.d.ts
vendored
Normal file
15
packages/core/admin/utils/index.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
import * as role from '../services/role';
|
||||
import * as user from '../services/user';
|
||||
import * as permission from '../services/permission';
|
||||
import * as contentType from '../services/content-type';
|
||||
import * as metrics from '../services/metrics';
|
||||
|
||||
type S = {
|
||||
role: typeof role;
|
||||
user: typeof user;
|
||||
permission: typeof permission;
|
||||
['content-type']: typeof contentType;
|
||||
metrics: typeof metrics;
|
||||
};
|
||||
|
||||
export function getService<T extends keyof S>(name: T): S[T];
|
||||
@ -6,7 +6,7 @@ const models = require('./models');
|
||||
const connections = require('./connections');
|
||||
|
||||
async function main() {
|
||||
const orm = new Database({
|
||||
const orm = await Database.init({
|
||||
connection: connections.mysql,
|
||||
models: Database.transformContentTypes(models),
|
||||
});
|
||||
@ -19,6 +19,8 @@ async function main() {
|
||||
|
||||
await orm.schema.reset();
|
||||
// await orm.schema.sync();
|
||||
|
||||
orm.query('article');
|
||||
} finally {
|
||||
orm.destroy();
|
||||
}
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
const errors = require('../errors');
|
||||
|
||||
class Dialect {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
useReturning() {
|
||||
return false;
|
||||
}
|
||||
@ -16,12 +20,15 @@ class Dialect {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
class PostgresDialect extends Dialect {
|
||||
useReturning() {
|
||||
return true;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.db.connection.client.types.setTypeParser(1700, 'text', parseFloat);
|
||||
}
|
||||
|
||||
transformErrors(error) {
|
||||
switch (error.code) {
|
||||
case '23502': {
|
||||
@ -35,12 +42,39 @@ class PostgresDialect extends Dialect {
|
||||
}
|
||||
|
||||
class MysqlDialect extends Dialect {
|
||||
initialize() {
|
||||
this.db.connection.supportBigNumbers = true;
|
||||
this.db.connection.bigNumberStrings = true;
|
||||
this.db.connection.typeCast = (field, next) => {
|
||||
if (field.type == 'DECIMAL' || field.type === 'NEWDECIMAL') {
|
||||
var value = field.string();
|
||||
return value === null ? null : Number(value);
|
||||
}
|
||||
|
||||
if (field.type == 'TINY' && field.length == 1) {
|
||||
console.log('coucou');
|
||||
|
||||
let value = field.string();
|
||||
return value ? value == '1' : null;
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
transformErrors(error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
class SqliteDialect extends Dialect {
|
||||
async initialize() {
|
||||
// Create the directory if it does not exist.
|
||||
|
||||
// options.connection.filename = path.resolve(strapi.config.appPath, options.connection.filename);
|
||||
|
||||
await this.db.connection.raw('PRAGMA foreign_keys = ON');
|
||||
}
|
||||
|
||||
transformErrors(error) {
|
||||
switch (error.errno) {
|
||||
case 19: {
|
||||
@ -53,18 +87,18 @@ class SqliteDialect extends Dialect {
|
||||
}
|
||||
}
|
||||
|
||||
const getDialect = connection => {
|
||||
const { client } = connection.client.config;
|
||||
const getDialect = db => {
|
||||
const { client } = db.connection.client.config;
|
||||
|
||||
switch (client) {
|
||||
case 'postgres':
|
||||
return new PostgresDialect();
|
||||
return new PostgresDialect(db);
|
||||
case 'mysql':
|
||||
return new MysqlDialect();
|
||||
return new MysqlDialect(db);
|
||||
case 'sqlite':
|
||||
return new SqliteDialect();
|
||||
return new SqliteDialect(db);
|
||||
default:
|
||||
return new Dialect();
|
||||
return new Dialect(db);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
const _ = require('lodash/fp');
|
||||
const types = require('./types');
|
||||
const { createField } = require('./fields');
|
||||
const { createQueryBuilder } = require('./query');
|
||||
const { createRepository } = require('./entity-repository');
|
||||
|
||||
@ -16,8 +17,15 @@ const pickRowAttributes = (metadata, data = {}) => {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (types.isScalar(attribute.type) && _.has(attributeName, data)) {
|
||||
// NOTE: we convert to column name
|
||||
obj[_.snakeCase(attributeName)] = data[attributeName];
|
||||
// TODO: we convert to column name
|
||||
// TODO: format data & use dialect to know which type they support (json particularly)
|
||||
|
||||
const field = createField(attribute.type, attribute);
|
||||
|
||||
// field.validate(data[attributeName]);
|
||||
|
||||
// TODO: handle default value too
|
||||
obj[attributeName] = field.toDB(data[attributeName]);
|
||||
}
|
||||
|
||||
if (types.isRelation(attribute.type)) {
|
||||
@ -41,263 +49,6 @@ const pickRowAttributes = (metadata, data = {}) => {
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach relations to a new entity
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> Id should have been added in the column of the model table beforehand to avoid extra updates
|
||||
* if joinTable
|
||||
* -> add relation
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> add relation
|
||||
* if joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> add relations in target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> Id should have been added in the column of the model table beforehand to avoid extra updates
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
* @param {object} data - data received for creation
|
||||
*/
|
||||
const attachRelations = async (em, metadata, id, data) => {
|
||||
const { attributes } = metadata;
|
||||
|
||||
// TODO: optimize later for createMany
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (attribute.joinColumn && attribute.owner) {
|
||||
// nothing to do => relation already added on the table
|
||||
continue;
|
||||
}
|
||||
|
||||
// oneToOne oneToMany on the non owning side
|
||||
if (attribute.joinColumn && !attribute.owner) {
|
||||
// need to set the column on the target
|
||||
const { target } = attribute;
|
||||
|
||||
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
|
||||
if (data[attributeName]) {
|
||||
await em
|
||||
.createQueryBuilder(target)
|
||||
.update({ [attribute.joinColumn.referencedColumn]: id })
|
||||
// NOTE: works if it is an array or a single id
|
||||
.where({ id: data[attributeName] })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.joinTable) {
|
||||
// need to set the column on the target
|
||||
|
||||
const { joinTable } = attribute;
|
||||
const { joinColumn, inverseJoinColumn } = joinTable;
|
||||
|
||||
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
|
||||
if (data[attributeName]) {
|
||||
const insert = Array.isArray(data[attributeName])
|
||||
? data[attributeName].map(datum => {
|
||||
return {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: datum,
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
})
|
||||
: {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: data[attributeName],
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
|
||||
await em
|
||||
.createQueryBuilder(joinTable.name)
|
||||
.insert(insert)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates relations of an existing entity
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> handled in the DB row
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> set join column on the target
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> set join column on the target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> handled in the DB row
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
* @param {object} data - data received for creation
|
||||
*/
|
||||
// TODO: check relation exists (handled by FKs except for polymorphics)
|
||||
const updateRelations = async (em, metadata, id, data) => {
|
||||
const { attributes } = metadata;
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
// NOTE: we do not remove existing associations with the target as it should handled by unique FKs instead
|
||||
if (attribute.joinColumn && attribute.owner) {
|
||||
// nothing to do => relation already added on the table
|
||||
continue;
|
||||
}
|
||||
|
||||
// oneToOne oneToMany on the non owning side.
|
||||
// Since it is a join column no need to remove previous relations
|
||||
if (attribute.joinColumn && !attribute.owner) {
|
||||
// need to set the column on the target
|
||||
const { target } = attribute;
|
||||
|
||||
if (data[attributeName]) {
|
||||
await em
|
||||
.createQueryBuilder(target)
|
||||
.update({ [attribute.joinColumn.referencedColumn]: id })
|
||||
// NOTE: works if it is an array or a single id
|
||||
.where({ id: data[attributeName] })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.joinTable) {
|
||||
const { joinTable } = attribute;
|
||||
const { joinColumn, inverseJoinColumn } = joinTable;
|
||||
|
||||
if (data[attributeName]) {
|
||||
// clear previous associations in the joinTable
|
||||
await em
|
||||
.createQueryBuilder(joinTable.name)
|
||||
.delete()
|
||||
.where({
|
||||
[joinColumn.name]: id,
|
||||
})
|
||||
// TODO: add join.on filters to only clear the valid info
|
||||
.where(joinTable.on ? joinTable.on : {})
|
||||
.execute();
|
||||
|
||||
// TODO: add pivot informations too
|
||||
const insert = Array.isArray(data[attributeName])
|
||||
? data[attributeName].map(datum => {
|
||||
return {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: datum,
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
})
|
||||
: {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: data[attributeName],
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
|
||||
console.log(insert);
|
||||
|
||||
await em
|
||||
.createQueryBuilder(joinTable.name)
|
||||
.insert(insert)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete relations of an existing entity
|
||||
* This removes associations but doesn't do cascade deletions for components for example. This will be handled on the entity service layer instead
|
||||
* NOTE: Most of the deletion should be handled by ON DELETE CASCADE
|
||||
*
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> handled in the DB row
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> set join column on the target // CASCADING should do the job
|
||||
* if joinTable
|
||||
* -> clear join Table assoc // CASCADING
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> set join column on the target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> handled in the DB row
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
*/
|
||||
// noop as cascade FKs does the job
|
||||
const deleteRelations = () => {};
|
||||
|
||||
const createEntityManager = db => {
|
||||
const repoMap = {};
|
||||
|
||||
@ -358,7 +109,7 @@ const createEntityManager = db => {
|
||||
.execute();
|
||||
|
||||
// create relation associations or move this to the entity service & call attach on the repo instead
|
||||
await attachRelations(this, metadata, id, data);
|
||||
await this.attachRelations(metadata, id, data);
|
||||
|
||||
// TODO: in case there is not select or populate specified return the inserted data ?
|
||||
|
||||
@ -418,7 +169,7 @@ const createEntityManager = db => {
|
||||
.execute();
|
||||
}
|
||||
|
||||
await updateRelations(this, metadata, id, data);
|
||||
await this.updateRelations(metadata, id, data);
|
||||
|
||||
return this.findOne(uid, { where: { id }, select: params.select, populate: params.populate });
|
||||
},
|
||||
@ -467,7 +218,7 @@ const createEntityManager = db => {
|
||||
.delete()
|
||||
.execute();
|
||||
|
||||
await deleteRelations(this, metadata, id);
|
||||
await this.deleteRelations(metadata, id);
|
||||
|
||||
return entity;
|
||||
},
|
||||
@ -505,10 +256,284 @@ const createEntityManager = db => {
|
||||
});
|
||||
},
|
||||
|
||||
// method to work with components & dynamic zones
|
||||
// addComponent() {},
|
||||
// removeComponent() {},
|
||||
// setComponent() {},
|
||||
/**
|
||||
* Attach relations to a new entity
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> Id should have been added in the column of the model table beforehand to avoid extra updates
|
||||
* if joinTable
|
||||
* -> add relation
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> add relation
|
||||
* if joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> add relations in target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> Id should have been added in the column of the model table beforehand to avoid extra updates
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
* @param {object} data - data received for creation
|
||||
*/
|
||||
async attachRelations(metadata, id, data) {
|
||||
const { attributes } = metadata;
|
||||
|
||||
// TODO: optimize later for createMany
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (attribute.joinColumn && attribute.owner) {
|
||||
// nothing to do => relation already added on the table
|
||||
continue;
|
||||
}
|
||||
|
||||
// oneToOne oneToMany on the non owning side
|
||||
if (attribute.joinColumn && !attribute.owner) {
|
||||
// need to set the column on the target
|
||||
const { target } = attribute;
|
||||
|
||||
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
|
||||
if (data[attributeName]) {
|
||||
await this.createQueryBuilder(target)
|
||||
.update({ [attribute.joinColumn.referencedColumn]: id })
|
||||
// NOTE: works if it is an array or a single id
|
||||
.where({ id: data[attributeName] })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.joinTable) {
|
||||
// need to set the column on the target
|
||||
|
||||
const { joinTable } = attribute;
|
||||
const { joinColumn, inverseJoinColumn } = joinTable;
|
||||
|
||||
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
|
||||
if (data[attributeName]) {
|
||||
const insert = _.castArray(data[attributeName]).map(datum => {
|
||||
return {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: datum,
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
});
|
||||
|
||||
// if there is nothing to insert
|
||||
if (insert.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createQueryBuilder(joinTable.name)
|
||||
.insert(insert)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates relations of an existing entity
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> handled in the DB row
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> set join column on the target
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> set join column on the target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> handled in the DB row
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
* @param {object} data - data received for creation
|
||||
*/
|
||||
// TODO: check relation exists (handled by FKs except for polymorphics)
|
||||
async updateRelations(metadata, id, data) {
|
||||
const { attributes } = metadata;
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
// NOTE: we do not remove existing associations with the target as it should handled by unique FKs instead
|
||||
if (attribute.joinColumn && attribute.owner) {
|
||||
// nothing to do => relation already added on the table
|
||||
continue;
|
||||
}
|
||||
|
||||
// oneToOne oneToMany on the non owning side.
|
||||
// Since it is a join column no need to remove previous relations
|
||||
if (attribute.joinColumn && !attribute.owner) {
|
||||
// need to set the column on the target
|
||||
const { target } = attribute;
|
||||
|
||||
if (data[attributeName]) {
|
||||
await this.createQueryBuilder(target)
|
||||
.update({ [attribute.joinColumn.referencedColumn]: id })
|
||||
// NOTE: works if it is an array or a single id
|
||||
.where({ id: data[attributeName] })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.joinTable) {
|
||||
const { joinTable } = attribute;
|
||||
const { joinColumn, inverseJoinColumn } = joinTable;
|
||||
|
||||
if (data[attributeName]) {
|
||||
// clear previous associations in the joinTable
|
||||
await this.createQueryBuilder(joinTable.name)
|
||||
.delete()
|
||||
.where({ [joinColumn.name]: id })
|
||||
.where(joinTable.on ? joinTable.on : {})
|
||||
.execute();
|
||||
|
||||
const insert = _.castArray(data[attributeName]).map(datum => {
|
||||
return {
|
||||
[joinColumn.name]: id,
|
||||
[inverseJoinColumn.name]: datum,
|
||||
...(joinTable.on || {}),
|
||||
};
|
||||
});
|
||||
|
||||
// if there is nothing to insert
|
||||
if (insert.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createQueryBuilder(joinTable.name)
|
||||
.insert(insert)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete relations of an existing entity
|
||||
* This removes associations but doesn't do cascade deletions for components for example. This will be handled on the entity service layer instead
|
||||
* NOTE: Most of the deletion should be handled by ON DELETE CASCADE
|
||||
*
|
||||
* oneToOne
|
||||
* if owner
|
||||
* if joinColumn
|
||||
* -> handled in the DB row
|
||||
* if joinTable
|
||||
* -> clear join Table assoc
|
||||
*
|
||||
* if not owner
|
||||
* if joinColumn
|
||||
* -> set join column on the target // CASCADING should do the job
|
||||
* if joinTable
|
||||
* -> clear join Table assoc // CASCADING
|
||||
*
|
||||
* oneToMany
|
||||
* owner -> cannot be owner
|
||||
* not owner
|
||||
* joinColumn
|
||||
* -> set join column on the target
|
||||
* joinTable
|
||||
* -> add relations in join table
|
||||
*
|
||||
* manyToOne
|
||||
* not owner -> must be owner
|
||||
* owner
|
||||
* join Column
|
||||
* -> handled in the DB row
|
||||
* joinTable
|
||||
* -> add relation in join table
|
||||
*
|
||||
* manyToMany
|
||||
* -> clear join Table assoc
|
||||
* -> add relation in join table
|
||||
*
|
||||
* @param {EntityManager} em - entity manager instance
|
||||
* @param {Metadata} metadata - model metadta
|
||||
* @param {ID} id - entity ID
|
||||
*/
|
||||
// noop as cascade FKs does the job
|
||||
async deleteRelations(metadata, id) {
|
||||
const { attributes } = metadata;
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
// NOTE: we do not remove existing associations with the target as it should handled by unique FKs instead
|
||||
if (attribute.joinColumn && attribute.owner) {
|
||||
// nothing to do => relation already added on the table
|
||||
continue;
|
||||
}
|
||||
|
||||
// oneToOne oneToMany on the non owning side.
|
||||
// Since it is a join column no need to remove previous relations
|
||||
if (attribute.joinColumn && !attribute.owner) {
|
||||
// need to set the column on the target
|
||||
const { target } = attribute;
|
||||
|
||||
await this.createQueryBuilder(target)
|
||||
.where({ [attribute.joinColumn.referencedColumn]: id })
|
||||
.delete()
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (attribute.joinTable) {
|
||||
const { joinTable } = attribute;
|
||||
const { joinColumn } = joinTable;
|
||||
|
||||
await this.createQueryBuilder(joinTable.name)
|
||||
.delete()
|
||||
.where({ [joinColumn.name]: id })
|
||||
.where(joinTable.on ? joinTable.on : {})
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// method to work with relations
|
||||
attachRelation() {},
|
||||
|
||||
7
packages/core/database/lib/fields.d.ts
vendored
Normal file
7
packages/core/database/lib/fields.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
interface Field {
|
||||
config: {};
|
||||
toDB(value: any): any;
|
||||
fromDB(value: any): any;
|
||||
}
|
||||
|
||||
export function createField(type: string): Field;
|
||||
@ -1,37 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
// simple example
|
||||
const dialects = {
|
||||
pg: {
|
||||
fields: {
|
||||
string: 'varchar',
|
||||
},
|
||||
},
|
||||
sqlite: {
|
||||
fields: {
|
||||
string: 'text',
|
||||
},
|
||||
},
|
||||
const _ = require('lodash/fp');
|
||||
|
||||
class Field {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// TODO: impl
|
||||
validate() {
|
||||
// // use config validators directly
|
||||
// if (this.config.validators) {
|
||||
// this.config.validators.forEach(validator => {
|
||||
// validator(value)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
toDB(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
fromDB(value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class StringField extends Field {
|
||||
toDB(value) {
|
||||
return _.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
class JSONField extends Field {
|
||||
toDB(value) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
class BooleanField extends Field {
|
||||
toDB(value) {
|
||||
if (typeof value === 'boolean') return value;
|
||||
|
||||
if (['true', 't', '1', 1].includes(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (['false', 'f', '0', 0].includes(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const typeToFieldMap = {
|
||||
increments: Field,
|
||||
password: StringField,
|
||||
email: StringField,
|
||||
string: StringField,
|
||||
uid: StringField,
|
||||
richtext: StringField,
|
||||
text: StringField,
|
||||
json: JSONField,
|
||||
enumeration: StringField,
|
||||
integer: Field,
|
||||
biginteger: StringField,
|
||||
float: Field,
|
||||
decimal: Field,
|
||||
date: Field,
|
||||
time: Field,
|
||||
datetime: Field,
|
||||
timestamp: Field,
|
||||
boolean: BooleanField,
|
||||
};
|
||||
|
||||
const FIELDS = {
|
||||
string: {
|
||||
defaultColumnType: 'varchar',
|
||||
// before write
|
||||
parser: a => a,
|
||||
// after read
|
||||
formatter: a => a,
|
||||
// before write
|
||||
validator: a => a,
|
||||
},
|
||||
const createField = (type /*attribute*/) => {
|
||||
if (_.has(type, typeToFieldMap)) {
|
||||
return new typeToFieldMap[type]({});
|
||||
}
|
||||
|
||||
throw new Error(`Undefined field for type ${type}`);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
get(type) {
|
||||
if (!(type in FIELDS)) {
|
||||
throw new Error(`Unknow field ${type}`);
|
||||
}
|
||||
|
||||
return FIELDS[type];
|
||||
},
|
||||
createField,
|
||||
};
|
||||
|
||||
// class ArrayField {
|
||||
// fields: Field[] = [];
|
||||
|
||||
// add(f: Field) {
|
||||
// this.fields.push(f);
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// remove(f: Field) {
|
||||
// this.fields.splice(this.fields.indexOf(f), 1);
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// wrapError(err, idx) {
|
||||
// return new Error(`Error on field ${idx + 1}: ${err.message}`);
|
||||
// }
|
||||
|
||||
// validate() {
|
||||
// return this.fields?.flatMap((field, idx) =>
|
||||
// field.validate().map(error => this.wrapError(error, idx))
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class GroupField {
|
||||
// fields: { [key: string]: Field } = {};
|
||||
|
||||
// add(key: string, f: Field) {
|
||||
// this.fields[key] = f;
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// remove(key: string) {
|
||||
// delete this.fields[key];
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// wrapError(err: Error, key: string) {
|
||||
// return new Error(`Error on field ${key}: ${err.message}`);
|
||||
// }
|
||||
|
||||
// validate() {
|
||||
// return Object.keys(this.fields).flatMap(key =>
|
||||
// this.fields[key].validate().map(error => this.wrapError(error, key))
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
8
packages/core/database/lib/index.d.ts
vendored
8
packages/core/database/lib/index.d.ts
vendored
@ -39,7 +39,15 @@ interface ModelConfig {
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
interface ConnectionConfig {}
|
||||
|
||||
interface DatabaseConfig {
|
||||
connection: ConnectionConfig;
|
||||
models: ModelConfig[];
|
||||
}
|
||||
|
||||
export class Database {
|
||||
static transformContentTypes(contentTypes: any[]): ModelConfig[];
|
||||
static init(config: DatabaseConfig): Database;
|
||||
query<T extends keyof AllTypes>(uid: T): QueryFromContentType<T>;
|
||||
}
|
||||
|
||||
@ -16,13 +16,13 @@ class Database {
|
||||
constructor(config) {
|
||||
this.metadata = createMetadata(config.models);
|
||||
|
||||
// TODO:; validate meta
|
||||
// TODO: validate meta
|
||||
// this.metadata.validate();
|
||||
|
||||
// this.connector = resolveConnector(this.config);
|
||||
|
||||
this.connection = knex(config.connection);
|
||||
this.dialect = getDialect(this.connection);
|
||||
this.dialect = getDialect(this);
|
||||
|
||||
this.schema = createSchemaProvider(this);
|
||||
|
||||
@ -31,6 +31,10 @@ class Database {
|
||||
this.entityManager = createEntityManager(this);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
await this.dialect.initialize();
|
||||
}
|
||||
|
||||
query(uid) {
|
||||
if (!this.metadata.has(uid)) {
|
||||
throw new Error(`Model ${uid} not found`);
|
||||
@ -45,6 +49,13 @@ class Database {
|
||||
}
|
||||
|
||||
Database.transformContentTypes = transformContentTypes;
|
||||
Database.init = async config => {
|
||||
const db = new Database(config);
|
||||
|
||||
await db.initialize();
|
||||
|
||||
return db;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Database,
|
||||
|
||||
@ -82,7 +82,6 @@ const createSchemaProvider = db => {
|
||||
|
||||
// TODO: replace by schemaDiff.hasChanged()
|
||||
if (status === 'UNCHANGED') {
|
||||
console.log('Unchanged');
|
||||
// NOTE: should we still update the schema in DB ?
|
||||
return;
|
||||
}
|
||||
|
||||
@ -272,13 +272,13 @@ class Strapi {
|
||||
cb();
|
||||
}
|
||||
|
||||
if (
|
||||
(this.config.environment === 'development' &&
|
||||
this.config.get('server.admin.autoOpen', true) !== false) ||
|
||||
!isInitialised
|
||||
) {
|
||||
await utils.openBrowser.call(this);
|
||||
}
|
||||
// if (
|
||||
// (this.config.environment === 'development' &&
|
||||
// this.config.get('server.admin.autoOpen', true) !== false) ||
|
||||
// !isInitialised
|
||||
// ) {
|
||||
// await utils.openBrowser.call(this);
|
||||
// }
|
||||
};
|
||||
|
||||
const listenSocket = this.config.get('server.socket');
|
||||
@ -361,7 +361,8 @@ class Strapi {
|
||||
...Object.values(strapi.api).flatMap(api => Object.values(api.models)),
|
||||
];
|
||||
|
||||
this.db = new Database({
|
||||
// TODO: create in RootProvider
|
||||
this.db = await Database.init({
|
||||
...this.config.get('database'),
|
||||
models: Database.transformContentTypes(contentTypes),
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user