refactor(database): model toJSON without the hidden fields

This commit is contained in:
chenos 2022-02-23 18:18:38 +08:00
parent e140227798
commit 872ee79146
20 changed files with 135 additions and 68 deletions

View File

@ -1,9 +1,10 @@
import merge from 'deepmerge'; import merge from 'deepmerge';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { default as lodash, default as _ } from 'lodash'; import { default as lodash, default as _ } from 'lodash';
import { Model, ModelCtor, ModelOptions, SyncOptions } from 'sequelize'; import { ModelCtor, ModelOptions, SyncOptions } from 'sequelize';
import { Database } from './database'; import { Database } from './database';
import { Field, FieldOptions } from './fields'; import { Field, FieldOptions } from './fields';
import { Model } from './model';
import { Repository } from './repository'; import { Repository } from './repository';
export type RepositoryType = typeof Repository; export type RepositoryType = typeof Repository;
@ -41,7 +42,7 @@ export class Collection<
context: CollectionContext; context: CollectionContext;
isThrough?: boolean; isThrough?: boolean;
fields: Map<string, any> = new Map<string, any>(); fields: Map<string, any> = new Map<string, any>();
model: ModelCtor<Model<TModelAttributes, TCreationAttributes>>; model: ModelCtor<Model>;
repository: Repository<TModelAttributes, TCreationAttributes>; repository: Repository<TModelAttributes, TCreationAttributes>;
get filterTargetKey() { get filterTargetKey() {
@ -81,12 +82,16 @@ export class Collection<
return; return;
} }
const { name, model, autoGenId = true } = this.options; const { name, model, autoGenId = true } = this.options;
let M = Model; let M: ModelCtor<Model> = Model;
if (this.context.database.sequelize.isDefined(name)) { if (this.context.database.sequelize.isDefined(name)) {
const m = this.context.database.sequelize.model(name); const m = this.context.database.sequelize.model(name);
if ((m as any).isThrough) { if ((m as any).isThrough) {
// @ts-ignore
this.model = m; this.model = m;
Object.defineProperty(this.model, 'database', { value: this.context.database }); // @ts-ignore
this.model.database = this.context.database;
// @ts-ignore
this.model.collection = this;
return; return;
} }
} }
@ -95,6 +100,7 @@ export class Collection<
} else if (model) { } else if (model) {
M = model; M = model;
} }
// @ts-ignore
this.model = class extends M {}; this.model = class extends M {};
this.model.init(null, this.sequelizeModelOptions()); this.model.init(null, this.sequelizeModelOptions());
@ -102,7 +108,10 @@ export class Collection<
this.model.removeAttribute('id'); this.model.removeAttribute('id');
} }
Object.defineProperty(this.model, 'database', { value: this.context.database }); // @ts-ignore
this.model.database = this.context.database;
// @ts-ignore
this.model.collection = this;
} }
setRepository(repository?: RepositoryType | string) { setRepository(repository?: RepositoryType | string) {

View File

@ -2,7 +2,6 @@ import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import merge from 'deepmerge'; import merge from 'deepmerge';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { import {
Model,
ModelCtor, ModelCtor,
Op, Op,
Options, Options,
@ -15,6 +14,7 @@ import { Collection, CollectionOptions, RepositoryType } from './collection';
import { ImporterReader, ImportFileExtension } from './collection-importer'; import { ImporterReader, ImportFileExtension } from './collection-importer';
import * as FieldTypes from './fields'; import * as FieldTypes from './fields';
import { Field, FieldContext, RelationField } from './fields'; import { Field, FieldContext, RelationField } from './fields';
import { Model } from './model';
import { ModelHook } from './model-hook'; import { ModelHook } from './model-hook';
import extendOperators from './operators'; import extendOperators from './operators';
import { RelationRepository } from './relation-repository/relation-repository'; import { RelationRepository } from './relation-repository/relation-repository';
@ -53,7 +53,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
sequelize: Sequelize; sequelize: Sequelize;
fieldTypes = new Map(); fieldTypes = new Map();
options: IDatabaseOptions; options: IDatabaseOptions;
models = new Map<string, ModelCtor<any>>(); models = new Map<string, ModelCtor<Model>>();
repositories = new Map<string, RepositoryType>(); repositories = new Map<string, RepositoryType>();
operators = new Map(); operators = new Map();
collections = new Map<string, Collection>(); collections = new Map<string, Collection>();

View File

@ -1,5 +1,6 @@
import { lodash } from '@umijs/utils'; import { lodash } from '@umijs/utils';
import { DataTypes, Model } from 'sequelize'; import { DataTypes } from 'sequelize';
import { Model } from '../model';
import { BaseColumnFieldOptions, Field } from './field'; import { BaseColumnFieldOptions, Field } from './field';
export class ContextField extends Field { export class ContextField extends Field {

View File

@ -1,9 +1,9 @@
import { Model, ModelCtor } from 'sequelize'; import { flatten } from 'flat';
import _ from 'lodash'; import { default as lodash, default as _ } from 'lodash';
import { flatten, unflatten } from 'flat'; import { ModelCtor } from 'sequelize';
import { Database } from './database';
import lodash from 'lodash';
import { Collection } from './collection'; import { Collection } from './collection';
import { Database } from './database';
import { Model } from './model';
const debug = require('debug')('noco-database'); const debug = require('debug')('noco-database');

View File

@ -1,10 +1,11 @@
export { Model, ModelCtor, SyncOptions } from 'sequelize'; export { ModelCtor, SyncOptions } from 'sequelize';
export * from './collection'; export * from './collection';
export * from './database'; export * from './database';
export { Database as default } from './database'; export { Database as default } from './database';
export * from './fields'; export * from './fields';
export * from './magic-attribute-model'; export * from './magic-attribute-model';
export * from './mock-database'; export * from './mock-database';
export * from './model';
export * from './relation-repository/belongs-to-many-repository'; export * from './relation-repository/belongs-to-many-repository';
export * from './relation-repository/belongs-to-repository'; export * from './relation-repository/belongs-to-repository';
export * from './relation-repository/hasmany-repository'; export * from './relation-repository/hasmany-repository';

View File

@ -1,7 +1,7 @@
import { Model } from 'sequelize';
import { merge } from '@nocobase/utils'; import { merge } from '@nocobase/utils';
import _ from 'lodash'; import _ from 'lodash';
import Database from './database'; import Database from './database';
import { Model } from './model';
export class MagicAttributeModel extends Model { export class MagicAttributeModel extends Model {
get magicAttribute() { get magicAttribute() {

View File

@ -1,7 +1,7 @@
import lodash from 'lodash'; import lodash from 'lodash';
import { Model } from 'sequelize';
import type { SequelizeHooks } from 'sequelize/types/lib/hooks'; import type { SequelizeHooks } from 'sequelize/types/lib/hooks';
import Database from './database'; import Database from './database';
import { Model } from './model';
const { hooks } = require('sequelize/lib/hooks'); const { hooks } = require('sequelize/lib/hooks');

View File

@ -0,0 +1,59 @@
import { Model as SequelizeModel } from 'sequelize';
import { Collection } from './collection';
import { Database } from './database';
interface IModel {
[key: string]: any;
}
export class Model<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes>
extends SequelizeModel<TModelAttributes, TCreationAttributes>
implements IModel
{
public static database: Database;
public static collection: Collection;
// [key: string]: any;
private toJsonWithoutHiddenFields(data, { model, collection }): any {
if (!data) {
return data;
}
const db = (this.constructor as any).database as Database;
const hidden = [];
collection.forEachField((field) => {
if (field.options.hidden) {
hidden.push(field.options.name);
}
});
const json = {};
Object.keys(data).forEach((key) => {
if (hidden.includes(key)) {
return;
}
if (model.hasAlias(key)) {
const association = model.associations[key];
const opts = {
model: association.target,
collection: db.getCollection(association.target.name),
};
if (['HasMany', 'BelongsToMany'].includes(association.associationType)) {
if (Array.isArray(data[key])) {
json[key] = data[key].map((item) => this.toJsonWithoutHiddenFields(item, opts));
}
} else {
json[key] = this.toJsonWithoutHiddenFields(data[key], opts);
}
} else {
json[key] = data[key];
}
});
return json;
}
public toJSON<T extends TModelAttributes>(): T {
return this.toJsonWithoutHiddenFields(super.toJSON(), {
model: this.constructor,
collection: (this.constructor as any).collection,
});
}
}

View File

@ -1,10 +1,11 @@
import { BelongsToMany, Model, Op, Transaction } from 'sequelize'; import lodash from 'lodash';
import { BelongsToMany, Op, Transaction } from 'sequelize';
import { Model } from '../model';
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
import { updateThroughTableValue } from '../update-associations'; import { updateThroughTableValue } from '../update-associations';
import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository'; import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository';
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
import lodash from 'lodash';
import { transaction } from './relation-repository'; import { transaction } from './relation-repository';
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
type CreateBelongsToManyOptions = CreateOptions; type CreateBelongsToManyOptions = CreateOptions;

View File

@ -1,7 +1,6 @@
import { Model } from 'sequelize'; import { Model } from '../model';
import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository';
import { CreateOptions, UpdateOptions } from '../repository'; import { CreateOptions, UpdateOptions } from '../repository';
import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository';
interface BelongsToFindOptions extends SingleRelationFindOption {} interface BelongsToFindOptions extends SingleRelationFindOption {}

View File

@ -1,14 +1,14 @@
import { BelongsToMany, HasMany, Model, Op, Sequelize } from 'sequelize'; import { omit } from 'lodash';
import { HasMany, Op } from 'sequelize';
import { Model } from '../model';
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, TK, UpdateOptions } from '../repository';
import { import {
AssociatedOptions, AssociatedOptions,
FindAndCountOptions, FindAndCountOptions,
FindOneOptions, FindOneOptions,
MultipleRelationRepository, MultipleRelationRepository
} from './multiple-relation-repository'; } from './multiple-relation-repository';
import { CreateOptions, DestroyOptions, FindOptions, TK, TargetKey, UpdateOptions } from '../repository';
import { transaction } from './relation-repository'; import { transaction } from './relation-repository';
import lodash, { omit } from 'lodash';
interface IHasManyRepository<M extends Model> { interface IHasManyRepository<M extends Model> {
find(options?: FindOptions): Promise<M>; find(options?: FindOptions): Promise<M>;

View File

@ -1,6 +1,6 @@
import { Model } from 'sequelize'; import { Model } from '../model';
import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository';
import { CreateOptions } from '../repository'; import { CreateOptions } from '../repository';
import { SingleRelationFindOption, SingleRelationRepository } from './single-relation-repository';
interface HasOneFindOptions extends SingleRelationFindOption {} interface HasOneFindOptions extends SingleRelationFindOption {}

View File

@ -1,14 +1,15 @@
import { Association, BelongsTo, BelongsToMany, HasMany, HasOne, Model, ModelCtor, Transaction } from 'sequelize';
import { OptionsParser } from '../options-parser';
import { Collection } from '../collection';
import { CreateOptions, Filter, FindOptions } from '../repository';
import FilterParser from '../filter-parser';
import { UpdateGuard } from '../update-guard';
import { updateAssociations } from '../update-associations';
import lodash from 'lodash'; import lodash from 'lodash';
import { transactionWrapperBuilder } from '../transaction-decorator'; import { Association, BelongsTo, BelongsToMany, HasMany, HasOne, ModelCtor, Transaction } from 'sequelize';
import { RelationField } from '../fields/relation-field'; import { Collection } from '../collection';
import Database from '../database'; import Database from '../database';
import { RelationField } from '../fields/relation-field';
import FilterParser from '../filter-parser';
import { Model } from '../model';
import { OptionsParser } from '../options-parser';
import { CreateOptions, Filter, FindOptions } from '../repository';
import { transactionWrapperBuilder } from '../transaction-decorator';
import { updateAssociations } from '../update-associations';
import { UpdateGuard } from '../update-guard';
export const transaction = transactionWrapperBuilder(function () { export const transaction = transactionWrapperBuilder(function () {
return this.sourceCollection.model.sequelize.transaction(); return this.sourceCollection.model.sequelize.transaction();

View File

@ -1,8 +1,9 @@
import { RelationRepository, transaction } from './relation-repository';
import { Model, SingleAssociationAccessors } from 'sequelize';
import { updateModelByValues } from '../update-associations';
import lodash from 'lodash'; import lodash from 'lodash';
import { SingleAssociationAccessors } from 'sequelize';
import { Model } from '../model';
import { Appends, Except, Fields, Filter, TargetKey, TransactionAble, UpdateOptions } from '../repository'; import { Appends, Except, Fields, Filter, TargetKey, TransactionAble, UpdateOptions } from '../repository';
import { updateModelByValues } from '../update-associations';
import { RelationRepository, transaction } from './relation-repository';
export interface SingleRelationFindOption extends TransactionAble { export interface SingleRelationFindOption extends TransactionAble {
fields?: Fields; fields?: Fields;

View File

@ -6,16 +6,16 @@ import {
DestroyOptions as SequelizeDestroyOptions, DestroyOptions as SequelizeDestroyOptions,
FindAndCountOptions as SequelizeAndCountOptions, FindAndCountOptions as SequelizeAndCountOptions,
FindOptions as SequelizeFindOptions, FindOptions as SequelizeFindOptions,
Model,
ModelCtor, ModelCtor,
Op, Op,
Transaction, Transaction,
UpdateOptions as SequelizeUpdateOptions, UpdateOptions as SequelizeUpdateOptions
} from 'sequelize'; } from 'sequelize';
import { Collection } from './collection'; import { Collection } from './collection';
import { Database } from './database'; import { Database } from './database';
import { RelationField } from './fields'; import { RelationField } from './fields';
import FilterParser from './filter-parser'; import FilterParser from './filter-parser';
import { Model } from './model';
import { OptionsParser } from './options-parser'; import { OptionsParser } from './options-parser';
import { BelongsToManyRepository } from './relation-repository/belongs-to-many-repository'; import { BelongsToManyRepository } from './relation-repository/belongs-to-many-repository';
import { BelongsToRepository } from './relation-repository/belongs-to-repository'; import { BelongsToRepository } from './relation-repository/belongs-to-repository';

View File

@ -5,10 +5,10 @@ import {
HasMany, HasMany,
HasOne, HasOne,
Hookable, Hookable,
Model,
ModelCtor, ModelCtor,
Transactionable, Transactionable
} from 'sequelize'; } from 'sequelize';
import { Model } from './model';
import { TransactionAble } from './repository'; import { TransactionAble } from './repository';
import { UpdateGuard } from './update-guard'; import { UpdateGuard } from './update-guard';
@ -257,11 +257,11 @@ export async function updateSingleAssociation(
let dataKey: string; let dataKey: string;
let M: ModelCtor<Model>; let M: ModelCtor<Model>;
if (association.associationType === 'BelongsTo') { if (association.associationType === 'BelongsTo') {
M = association.target; M = association.target as ModelCtor<Model>;
// @ts-ignore // @ts-ignore
dataKey = association.targetKey; dataKey = association.targetKey;
} else { } else {
M = association.source; M = association.source as ModelCtor<Model>;
dataKey = M.primaryKeyAttribute; dataKey = M.primaryKeyAttribute;
} }
@ -396,7 +396,7 @@ export async function updateMultipleAssociation(
list3.push(instance); list3.push(instance);
} else { } else {
// set & update record // set & update record
const instance = await association.target.findByPk(item[pk], { const instance = await association.target.findByPk<any>(item[pk], {
transaction, transaction,
}); });
const addAccessor = association.accessors.add; const addAccessor = association.accessors.add;

View File

@ -1,8 +1,8 @@
import lodash from 'lodash'; import lodash from 'lodash';
import { Model, ModelCtor } from 'sequelize'; import { ModelCtor } from 'sequelize';
import { Model } from './model';
import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository'; import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository';
type UpdateValueItem = string | number | UpdateValues; type UpdateValueItem = string | number | UpdateValues;
type UpdateValues = { type UpdateValues = {

View File

@ -19,7 +19,7 @@ export async function signin(ctx: Context, next: Next) {
ctx.throw(401, '请填写邮箱账号'); ctx.throw(401, '请填写邮箱账号');
} }
const User = ctx.db.getCollection('users'); const User = ctx.db.getCollection('users');
const user = await User.model.scope('withPassword').findOne<any>({ const user = await User.model.findOne<any>({
where: { where: {
[uniqueField]: values[uniqueField], [uniqueField]: values[uniqueField],
}, },
@ -36,8 +36,10 @@ export async function signin(ctx: Context, next: Next) {
user.token = crypto.randomBytes(20).toString('hex'); user.token = crypto.randomBytes(20).toString('hex');
await user.save(); await user.save();
} }
ctx.body = user.toJSON(); ctx.body = {
delete ctx.body.password; ...user.toJSON(),
token: user.get('token'),
};
await next(); await next();
} }
@ -47,10 +49,10 @@ export async function signout(ctx: Context, next: Next) {
} }
export async function signup(ctx: Context, next: Next) { export async function signup(ctx: Context, next: Next) {
const User = ctx.db.getCollection('users'); const User = ctx.db.getRepository('users');
const { values } = ctx.action.params; const { values } = ctx.action.params;
try { try {
const user = await User.model.create(values); const user = await User.create({ values });
ctx.body = user; ctx.body = user;
} catch (error) { } catch (error) {
if (error.errors) { if (error.errors) {
@ -139,7 +141,7 @@ export async function changePassword(ctx: Context, next: Next) {
ctx.throw(401, 'Unauthorized'); ctx.throw(401, 'Unauthorized');
} }
const User = ctx.db.getCollection('users'); const User = ctx.db.getCollection('users');
const user = await User.model.scope('withPassword').findOne<any>({ const user = await User.model.findOne<any>({
where: { where: {
email: ctx.state.currentUser.email, email: ctx.state.currentUser.email,
}, },

View File

@ -4,14 +4,6 @@ export default {
name: 'users', name: 'users',
title: '{{t("Users")}}', title: '{{t("Users")}}',
sortable: 'sort', sortable: 'sort',
scopes: {
withPassword: {
attributes: { include: ['password'] },
},
},
defaultScope: {
attributes: { exclude: ['password'] },
},
fields: [ fields: [
{ {
interface: 'string', interface: 'string',
@ -39,6 +31,7 @@ export default {
interface: 'password', interface: 'password',
type: 'password', type: 'password',
name: 'password', name: 'password',
hidden: true,
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: '{{t("Password")}}', title: '{{t("Password")}}',

View File

@ -1,10 +1,10 @@
import glob from 'glob'; import glob from 'glob';
import compose from 'koa-compose'; import compose from 'koa-compose';
import _ from 'lodash';
import { pathToRegexp } from 'path-to-regexp';
import Action, { ActionName } from './action'; import Action, { ActionName } from './action';
import Resource, { ResourceOptions } from './resource'; import Resource, { ResourceOptions } from './resource';
import { parseRequest, getNameByParams, ParsedParams, requireModule, parseQuery } from './utils'; import { getNameByParams, ParsedParams, parseQuery, parseRequest, requireModule } from './utils';
import { pathToRegexp } from 'path-to-regexp';
import _ from 'lodash';
export interface ResourcerContext { export interface ResourcerContext {
resourcer?: Resourcer; resourcer?: Resourcer;