Init fields

This commit is contained in:
Alexandre Bodin 2021-06-28 12:34:29 +02:00
parent d33a363c88
commit a1e2fc6829
17 changed files with 574 additions and 356 deletions

View File

@ -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',

View File

@ -47,7 +47,7 @@ module.exports = async () => {
await roleService.createRolesIfNoneExist();
// await roleService.resetSuperAdminPermissions();
await roleService.resetSuperAdminPermissions();
await roleService.displayWarningIfNoSuperAdmin();
// await permissionService.ensureBoundPermissionsInDatabase();

View File

@ -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',

View File

@ -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,

View File

@ -0,0 +1 @@
export * from './role';

View File

@ -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(

View File

@ -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
View 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];

View File

@ -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();
}

View File

@ -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);
}
};

View File

@ -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() {},

View File

@ -0,0 +1,7 @@
interface Field {
config: {};
toDB(value: any): any;
fromDB(value: any): any;
}
export function createField(type: string): Field;

View File

@ -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))
// );
// }
// }

View File

@ -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>;
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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),
});