diff --git a/package.json b/package.json index a8227f7a..62408927 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "sanitize-html": "^2.11.0", - "sequelize": "^6.19.0", + "sequelize": "^6.30.0", "statuses": "^2.0.1", "tedious": "^15.1.0", "wildcard": "^2.0.1" diff --git a/servers/identity-server/src/app.module.ts b/servers/identity-server/src/app.module.ts index 4ed74fef..61c1bbcc 100644 --- a/servers/identity-server/src/app.module.ts +++ b/servers/identity-server/src/app.module.ts @@ -217,9 +217,10 @@ const logger = new Logger('AppModule', { timestamp: true }); isGlobal: true, useFactory: async (config: ConfigService, storageOptions: StorageOptions) => ({ debug: config.get('debug', false), - issuer: `${config.getOrThrow('server.origin')}${normalizeRoutePath( - config.get('server.globalPrefixUri', ''), - )}`, + issuer: 'http://fakeissuer.com', + // `${config.getOrThrow('server.origin')}${normalizeRoutePath( + // config.get('server.globalPrefixUri', ''), + // )}`, path: normalizeRoutePath(config.get('OIDC_PATH', '/oidc')), jwks: await getJWKS(config.get('PRIVATE_KEY')), storage: storageOptions.use, diff --git a/servers/identity-server/src/datasource/constants.ts b/servers/identity-server/src/datasource/constants.ts index 5044e9f6..4efdcabb 100644 --- a/servers/identity-server/src/datasource/constants.ts +++ b/servers/identity-server/src/datasource/constants.ts @@ -1 +1 @@ -export const IDENTITY_OPTIONS = 'IDENTITY_OPTIONS'; +export const IDENTITY_DATASOURCE_OPTIONS = 'IDENTITY_DATASOURCE_OPTIONS'; diff --git a/servers/identity-server/src/datasource/datasource.module.ts b/servers/identity-server/src/datasource/datasource.module.ts index 42a6409b..7b9244f0 100644 --- a/servers/identity-server/src/datasource/datasource.module.ts +++ b/servers/identity-server/src/datasource/datasource.module.ts @@ -4,9 +4,11 @@ import { IdentityDatasourceAsyncOptions, IdentityDatasourceOptionsFactory, } from './interfaces/identity-datasource-options.interface'; -import { dataSources } from './sequelize'; +import * as DataSources from './sequelize/datasources'; import { IdentityDatasourceService } from './datasource.service'; -import { IDENTITY_OPTIONS } from './constants'; +import { IDENTITY_DATASOURCE_OPTIONS } from './constants'; + +const dataSources = Object.values(DataSources); @Module({}) export class IdentityDatasourceModule { @@ -22,13 +24,13 @@ export class IdentityDatasourceModule { global: isGlobal, providers: [ { - provide: IDENTITY_OPTIONS, + provide: IDENTITY_DATASOURCE_OPTIONS, useValue: restOptions, }, - ...dataSources, IdentityDatasourceService, + ...dataSources, ], - exports: [IDENTITY_OPTIONS, ...dataSources, IdentityDatasourceService], + exports: [IDENTITY_DATASOURCE_OPTIONS, IdentityDatasourceService, ...dataSources], }; } @@ -37,8 +39,8 @@ export class IdentityDatasourceModule { module: IdentityDatasourceModule, global: options.isGlobal, imports: options.imports, - providers: [...this.createAsyncProviders(options), ...dataSources, IdentityDatasourceService], - exports: [IDENTITY_OPTIONS, ...dataSources, IdentityDatasourceService], + providers: [...this.createAsyncProviders(options), IdentityDatasourceService, ...dataSources], + exports: [IDENTITY_DATASOURCE_OPTIONS, IdentityDatasourceService, ...dataSources], }; } @@ -58,7 +60,7 @@ export class IdentityDatasourceModule { private static createAsyncOptionsProvider(options: IdentityDatasourceAsyncOptions): Provider { if (options.useFactory) { return { - provide: IDENTITY_OPTIONS, + provide: IDENTITY_DATASOURCE_OPTIONS, useFactory: async (...args: any[]) => { const moduleOptions = await options.useFactory!(...args); // check connection config @@ -69,7 +71,7 @@ export class IdentityDatasourceModule { }; } return { - provide: IDENTITY_OPTIONS, + provide: IDENTITY_DATASOURCE_OPTIONS, useFactory: async (optionsFactory: IdentityDatasourceOptionsFactory) => { const moduleOptions = await optionsFactory.createSequlizeOptions(); // check connection config diff --git a/servers/identity-server/src/datasource/datasource.service.ts b/servers/identity-server/src/datasource/datasource.service.ts index 7e8392b9..1f23bc10 100644 --- a/servers/identity-server/src/datasource/datasource.service.ts +++ b/servers/identity-server/src/datasource/datasource.service.ts @@ -1,30 +1,362 @@ -import { Sequelize } from 'sequelize'; -import { Injectable, Inject, OnApplicationShutdown } from '@nestjs/common'; +import { Injectable, Inject, Logger, OnApplicationShutdown } from '@nestjs/common'; +import { SyncOptions, CreationAttributes } from 'sequelize'; import { IdentityDatasourceOptions } from './interfaces/identity-datasource-options.interface'; -import { DatabaseManager } from './sequelize'; -import { Models } from './sequelize/interfaces/table-associate-func.interface'; -import { IDENTITY_OPTIONS } from './constants'; +import { DataInitArgs } from './interfaces/data-init-args.interface'; +import { Sequelize, SequelizeOptions } from './sequelize/sequelize'; +import { + ApiResources, + ApiClaims, + ApiScopeClaims, + ApiScopes, + ApiSecrets, + ApiProperties, + IdentityResources, + IdentityClaims, + IdentityProperties, + Clients, + ClientClaims, + ClientCorsOrigins, + ClientScopes, + ClientGrantTypes, + ClientPostLogoutRedirectUris, + ClientRedirectUris, + ClientSecrets, + ClientProperties, +} from './sequelize/entities'; +import { IDENTITY_DATASOURCE_OPTIONS } from './constants'; @Injectable() export class IdentityDatasourceService implements OnApplicationShutdown { - sequelize: Sequelize; - models: Models; - // initDatas: DatabaseManager['initDatas']; + private readonly logger = new Logger(Sequelize.name, { timestamp: true }); + sequelize: Readonly; - constructor(@Inject(IDENTITY_OPTIONS) options: IdentityDatasourceOptions) { - const dbManager = + constructor(@Inject(IDENTITY_DATASOURCE_OPTIONS) private readonly options: IdentityDatasourceOptions) { + const sequelizeOptions: SequelizeOptions = { + models: [ + ApiResources, + ApiClaims, + ApiScopeClaims, + ApiScopes, + ApiSecrets, + ApiProperties, + IdentityResources, + IdentityClaims, + IdentityProperties, + Clients, + ClientClaims, + ClientCorsOrigins, + ClientScopes, + ClientGrantTypes, + ClientPostLogoutRedirectUris, + ClientRedirectUris, + ClientSecrets, + ClientProperties, + ], + tablePrefix: options.tablePrefix, + define: { + freezeTableName: true, + underscored: true, + timestamps: true, + createdAt: true, + updatedAt: true, + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_520_ci', + }, + }; + this.sequelize = typeof options.connection === 'string' - ? new DatabaseManager(options.connection, { - tablePrefix: options.tablePrefix, - }) - : new DatabaseManager({ + ? new Sequelize(options.connection, sequelizeOptions) + : new Sequelize({ + ...sequelizeOptions, ...options.connection, - tablePrefix: options.tablePrefix, + define: { + ...sequelizeOptions.define, + ...options.connection.define, + }, }); + } + + get tablePrefix() { + return this.options.tablePrefix || ''; + } + + get translate() { + return this.options.translate || ((key, fallback) => fallback); + } + + /** + * 初始化数据库 + * @author Hubert + * @since 2022-05-01 + * @version 0.0.1 + * @param options 初始化参数 + * @returns true: 生成数据库成功;false: 跳过数据库生成(when 条件不满足) 否则抛出 Error + */ + async syncDB( + options?: SyncOptions & { when?: boolean | ((sequelize: Readonly) => Promise) }, + ): Promise { + try { + await this.sequelize.authenticate(); + } catch (err: any) { + this.logger.error(`Unable to connect to the database, Error: ${err.message}`); + throw err; + } + + try { + // eslint-disable-next-line prefer-const + let { when = true, ...syncOptions } = options || {}; + if (typeof when === 'function') { + when = await when.call(null, this.sequelize); + } + if (when) { + await this.sequelize.sync(syncOptions); + return true; + } + return false; + } catch (err: any) { + this.logger.error(`Unable to sync to the database, Error: ${err.message}`); + throw err; + } + } + + /** + * 实始化数据(必须在DB初始化表结构后调用) + * @author Hubert + * @since 2022-05-01 + * @version 0.0.1 + * @param initArgs 初始化参数 + */ + async initDatas(initArgs: DataInitArgs): Promise { + const t = await this.sequelize.transaction(); + try { + if (initArgs.identityResources?.length) { + // Initialize identity resources + const identityResources = await IdentityResources.bulkCreate( + initArgs.identityResources.map( + ({ claims: _ignored0, properties: _ignored1, ...identityResource }) => identityResource, + ), + { transaction: t }, + ); + + const identityClaimsCreation = initArgs.identityResources.reduce((prev, item, index) => { + return prev.concat( + item.claims?.map((claim) => ({ + identityResourceId: identityResources[index].id, + type: claim, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + identityClaimsCreation.length && (await IdentityClaims.bulkCreate(identityClaimsCreation, { transaction: t })); + + const identityPropertiesCreation = initArgs.identityResources.reduce((prev, item, index) => { + return prev.concat( + item.properties?.map((property) => ({ + identityResourceId: identityResources[index].id, + ...property, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + identityPropertiesCreation.length && + (await IdentityProperties.bulkCreate(identityPropertiesCreation, { transaction: t })); + } + + // Initialize api resources + if (initArgs.apiResources?.length) { + const apiResources = await ApiResources.bulkCreate( + initArgs.apiResources.map( + ({ claims: _ignored0, scopes: _ignored1, secrets: _ignored2, properties: _ignored3, ...apiResource }) => + apiResource, + ), + { transaction: t }, + ); + + const apiClaimsCreation = initArgs.apiResources.reduce((prev, item, index) => { + return prev.concat( + item.claims?.map((claim) => ({ + apiResourceId: apiResources[index].id, + type: claim, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + apiClaimsCreation.length && (await ApiClaims.bulkCreate(apiClaimsCreation, { transaction: t })); + + const apiScopesCreation = initArgs.apiResources.reduce((prev, item, index) => { + return prev.concat( + item.scopes?.map(({ claims: _ignored0, ...scope }) => ({ + apiResourceId: apiResources[index].id, + ...scope, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + const apiScopes = apiScopesCreation.length + ? await ApiScopes.bulkCreate(apiScopesCreation, { transaction: t }) + : []; + + let scopeClaimsIndex = 0; + const apiScopeClaimsCreation = initArgs.apiResources.reduce((prev, item) => { + item.scopes?.forEach((scope) => { + prev = prev.concat( + scope.claims?.map((claim) => ({ + apiScopeId: apiScopes[scopeClaimsIndex].id, + type: claim, + })) ?? [], + ); + scopeClaimsIndex++; + }); + return prev; + }, [] as CreationAttributes[]); + + apiScopeClaimsCreation.length && (await ApiScopeClaims.bulkCreate(apiScopeClaimsCreation, { transaction: t })); + + const apiSecretsCreation = initArgs.apiResources.reduce((prev, item, index) => { + return prev.concat( + item.secrets?.map((secret) => ({ + apiResourceId: apiResources[index].id, + ...secret, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + apiSecretsCreation.length && (await ApiSecrets.bulkCreate(apiSecretsCreation, { transaction: t })); + + const apiPropertiesCreation = initArgs.apiResources.reduce((prev, item, index) => { + return prev.concat( + item.properties?.map((property) => ({ + apiResourceId: apiResources[index].id, + ...property, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + apiPropertiesCreation.length && (await ApiProperties.bulkCreate(apiPropertiesCreation, { transaction: t })); + } + + // Initialize clients + if (initArgs.clients?.length) { + const clients = await Clients.bulkCreate( + initArgs.clients.map( + ({ + claims: _ignored0, + corsOrigins: _ignored1, + scopes: _ignored2, + grantTypes: _ignored3, + redirectUris: _ignored4, + postLogoutRedirectUris: _ignored5, + secrets: _ignored6, + properties: _ignored7, + ...client + }) => client, + ), + { transaction: t }, + ); + + const clientClaimsCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.claims?.map((claim) => ({ + clientId: clients[index].id, + type: claim.type, + value: claim.value, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientClaimsCreation.length && (await ClientClaims.bulkCreate(clientClaimsCreation, { transaction: t })); + + const clientCorsOriginsCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.corsOrigins?.map((origin) => ({ + clientId: clients[index].id, + origin, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientCorsOriginsCreation.length && + (await ClientCorsOrigins.bulkCreate(clientCorsOriginsCreation, { transaction: t })); + + const clientScopesCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.scopes?.map((scope) => ({ + clientId: clients[index].id, + scope, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientScopesCreation.length && (await ClientScopes.bulkCreate(clientScopesCreation, { transaction: t })); + + const clientGrantTypesCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.grantTypes?.map((grantType) => ({ + clientId: clients[index].id, + grantType, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientGrantTypesCreation.length && + (await ClientGrantTypes.bulkCreate(clientGrantTypesCreation, { transaction: t })); + + const clientRedirectUrisCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.redirectUris?.map((redirectUri) => ({ + clientId: clients[index].id, + redirectUri, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientRedirectUrisCreation.length && + (await ClientRedirectUris.bulkCreate(clientRedirectUrisCreation, { transaction: t })); + + const clientPostLogoutRedirectUrisCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.postLogoutRedirectUris?.map((postLogoutRedirectUri) => ({ + clientId: clients[index].id, + postLogoutRedirectUri, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientPostLogoutRedirectUrisCreation.length && + (await ClientPostLogoutRedirectUris.bulkCreate(clientPostLogoutRedirectUrisCreation, { + transaction: t, + })); + + const clientSecretsCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.secrets?.map((secret) => ({ + clientId: clients[index].id, + ...secret, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientSecretsCreation.length && (await ClientSecrets.bulkCreate(clientSecretsCreation, { transaction: t })); + + const clientPropertiesCreation = initArgs.clients.reduce((prev, item, index) => { + return prev.concat( + item.properties?.map((property) => ({ + clientId: clients[index].id, + ...property, + })) ?? [], + ); + }, [] as CreationAttributes[]); + + clientPropertiesCreation.length && + (await ClientProperties.bulkCreate(clientPropertiesCreation, { transaction: t })); + } - this.sequelize = dbManager.sequelize; - this.models = dbManager.associate(); - // this.initDatas = dbManager.initDatas.bind(dbManager); + await t.commit(); + return true; + } catch (err) { + await t.rollback(); + throw err; + } } onApplicationShutdown() { diff --git a/servers/identity-server/src/datasource/entities/api-claims.entity.ts b/servers/identity-server/src/datasource/entities/api-claims.entity.ts index 734018bc..8d56a8cc 100644 --- a/servers/identity-server/src/datasource/entities/api-claims.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-claims.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiClaimsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/api-properties.entity.ts b/servers/identity-server/src/datasource/entities/api-properties.entity.ts index b2c7e364..b29f3605 100644 --- a/servers/identity-server/src/datasource/entities/api-properties.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-properties.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiPropertiesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/api-resources.entity.ts b/servers/identity-server/src/datasource/entities/api-resources.entity.ts index 895ce3ff..aad4c253 100644 --- a/servers/identity-server/src/datasource/entities/api-resources.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-resources.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiResourcesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/api-scope-claims.entity.ts b/servers/identity-server/src/datasource/entities/api-scope-claims.entity.ts index 1d53ef26..8e0d5410 100644 --- a/servers/identity-server/src/datasource/entities/api-scope-claims.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-scope-claims.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiScopeClaimsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/api-scopes.entity.ts b/servers/identity-server/src/datasource/entities/api-scopes.entity.ts index f384f111..4fe7cd6d 100644 --- a/servers/identity-server/src/datasource/entities/api-scopes.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-scopes.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiScopesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/api-secrets.entity.ts b/servers/identity-server/src/datasource/entities/api-secrets.entity.ts index 363036bc..912b3e62 100644 --- a/servers/identity-server/src/datasource/entities/api-secrets.entity.ts +++ b/servers/identity-server/src/datasource/entities/api-secrets.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ApiSecretsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-claims.entity.ts b/servers/identity-server/src/datasource/entities/client-claims.entity.ts index c6a21f4d..8eec1218 100644 --- a/servers/identity-server/src/datasource/entities/client-claims.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-claims.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientClaimsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-cors-origins.entity.ts b/servers/identity-server/src/datasource/entities/client-cors-origins.entity.ts index 1d42deca..c9be3774 100644 --- a/servers/identity-server/src/datasource/entities/client-cors-origins.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-cors-origins.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientCorsOriginsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-grant-types.entity.ts b/servers/identity-server/src/datasource/entities/client-grant-types.entity.ts index b454ab50..919497e5 100644 --- a/servers/identity-server/src/datasource/entities/client-grant-types.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-grant-types.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientGrantTypesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-id-prestrictions.entity.ts b/servers/identity-server/src/datasource/entities/client-id-prestrictions.entity.ts index 7d1e0c65..618c8591 100644 --- a/servers/identity-server/src/datasource/entities/client-id-prestrictions.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-id-prestrictions.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientIdPRestrictionsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-post-logout-redirect-uris.entity.ts b/servers/identity-server/src/datasource/entities/client-post-logout-redirect-uris.entity.ts index 54c9ced5..9f2d6479 100644 --- a/servers/identity-server/src/datasource/entities/client-post-logout-redirect-uris.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-post-logout-redirect-uris.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientPostLogoutRedirectUrisAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-properties.entity.ts b/servers/identity-server/src/datasource/entities/client-properties.entity.ts index 145aff37..1c55b42b 100644 --- a/servers/identity-server/src/datasource/entities/client-properties.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-properties.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientPropertiesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-redirect-uris.entity.ts b/servers/identity-server/src/datasource/entities/client-redirect-uris.entity.ts index 27a92dfc..8c4ccfe8 100644 --- a/servers/identity-server/src/datasource/entities/client-redirect-uris.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-redirect-uris.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientRedirectUrisAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-scopes.entity.ts b/servers/identity-server/src/datasource/entities/client-scopes.entity.ts index 1d02084a..6de0ed6a 100644 --- a/servers/identity-server/src/datasource/entities/client-scopes.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-scopes.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientScopesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/client-secrets.entity.ts b/servers/identity-server/src/datasource/entities/client-secrets.entity.ts index c9ed1c4f..ceffb4e4 100644 --- a/servers/identity-server/src/datasource/entities/client-secrets.entity.ts +++ b/servers/identity-server/src/datasource/entities/client-secrets.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientSecretsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/clients.entity.ts b/servers/identity-server/src/datasource/entities/clients.entity.ts index c0135a74..501d9b7e 100644 --- a/servers/identity-server/src/datasource/entities/clients.entity.ts +++ b/servers/identity-server/src/datasource/entities/clients.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface ClientsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/identity-claims.entity.ts b/servers/identity-server/src/datasource/entities/identity-claims.entity.ts index c505631c..494ebfeb 100644 --- a/servers/identity-server/src/datasource/entities/identity-claims.entity.ts +++ b/servers/identity-server/src/datasource/entities/identity-claims.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface IdentityClaimsAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/identity-properties.entity.ts b/servers/identity-server/src/datasource/entities/identity-properties.entity.ts index cac62036..b708f3ec 100644 --- a/servers/identity-server/src/datasource/entities/identity-properties.entity.ts +++ b/servers/identity-server/src/datasource/entities/identity-properties.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface IdentityPropertiesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/entities/identity-resources.entity.ts b/servers/identity-server/src/datasource/entities/identity-resources.entity.ts index 333b93a5..298a3b47 100644 --- a/servers/identity-server/src/datasource/entities/identity-resources.entity.ts +++ b/servers/identity-server/src/datasource/entities/identity-resources.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface IdentityResourcesAttributes { id: number; diff --git a/servers/identity-server/src/datasource/index.ts b/servers/identity-server/src/datasource/index.ts index 5e4b2b70..b7847866 100644 --- a/servers/identity-server/src/datasource/index.ts +++ b/servers/identity-server/src/datasource/index.ts @@ -4,4 +4,4 @@ export * from './datasource.module'; export * from './datasource.service'; export * from './interfaces'; export * from './sequelize'; -export * from './entities/types'; +export * from './entities'; diff --git a/servers/identity-server/src/datasource/interfaces/data-init-args.interface.ts b/servers/identity-server/src/datasource/interfaces/data-init-args.interface.ts new file mode 100644 index 00000000..c019c7af --- /dev/null +++ b/servers/identity-server/src/datasource/interfaces/data-init-args.interface.ts @@ -0,0 +1,54 @@ +import { CreationAttributes } from 'sequelize'; +import { + ApiResources, + ApiClaims, + ApiScopes, + ApiScopeClaims, + ApiSecrets, + ApiProperties, + IdentityResources, + IdentityClaims, + IdentityProperties, + Clients, + ClientClaims, + ClientCorsOrigins, + ClientScopes, + ClientGrantTypes, + ClientRedirectUris, + ClientPostLogoutRedirectUris, + ClientSecrets, + ClientProperties, +} from '../sequelize/entities'; + +export interface DataInitArgs { + apiResources?: Array< + CreationAttributes & { + claims?: CreationAttributes['type'][]; + scopes?: Array< + Omit, 'apiResourceId'> & { + claims?: CreationAttributes['type'][]; + } + >; + secrets?: Omit, 'apiResourceId'>[]; + properties?: Omit, 'apiResourceId'>[]; + } + >; + identityResources?: Array< + CreationAttributes & { + claims?: CreationAttributes['type'][]; + properties?: Omit, 'identityResourceId'>[]; + } + >; + clients?: Array< + CreationAttributes & { + claims?: Omit, 'clientId'>[]; + corsOrigins?: CreationAttributes['origin'][]; + scopes?: CreationAttributes['scope'][]; + grantTypes?: CreationAttributes['grantType'][]; + redirectUris?: CreationAttributes['redirectUri'][]; + postLogoutRedirectUris?: CreationAttributes['postLogoutRedirectUri'][]; + secrets?: Omit, 'clientId'>[]; + properties?: Omit, 'clientId'>[]; + } + >; +} diff --git a/servers/identity-server/src/datasource/interfaces/index.ts b/servers/identity-server/src/datasource/interfaces/index.ts index ec9387e8..c7059bf9 100644 --- a/servers/identity-server/src/datasource/interfaces/index.ts +++ b/servers/identity-server/src/datasource/interfaces/index.ts @@ -1 +1,2 @@ export * from './identity-datasource-options.interface'; +export * from './data-init-args.interface'; diff --git a/servers/identity-server/src/datasource/sequelize/datasources/api-resource.datasource.ts b/servers/identity-server/src/datasource/sequelize/datasources/api-resource.datasource.ts index 51449907..87d92b4a 100644 --- a/servers/identity-server/src/datasource/sequelize/datasources/api-resource.datasource.ts +++ b/servers/identity-server/src/datasource/sequelize/datasources/api-resource.datasource.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { WhereOptions, Order, Attributes, Op, Includeable } from 'sequelize'; import { ValidationError } from '@ace-pomelo/shared/server'; -import { default as ApiResources } from '../entities/api-resources.entity'; -import { default as ApiScopes } from '../entities/api-scopes.entity'; +import { IdentityDatasourceService } from '../../datasource.service'; +import { ApiResources, ApiScopes, ApiClaims, ApiSecrets, ApiProperties, ApiScopeClaims } from '../entities'; import { ApiResourceModel, PagedApiResourceArgs, @@ -27,13 +27,12 @@ import { ApiPropertiesModel, NewApiPropertyInput, } from '../interfaces/api-resource.interface'; -import { IdentityDatasourceService } from '../../datasource.service'; import { BaseDataSource } from './base.datasource'; @Injectable() export class ApiResourceDataSource extends BaseDataSource { - constructor(protected readonly datasourceService: IdentityDatasourceService) { - super(); + constructor(datasourceService: IdentityDatasourceService) { + super(datasourceService); } /** @@ -58,29 +57,29 @@ export class ApiResourceDataSource extends BaseDataSource { fields.unshift('id'); } - return this.models.ApiResources.findByPk(id, { - attributes: this.filterFields(fields, this.models.ApiResources), + return ApiResources.findByPk(id, { + attributes: this.filterFields(fields, ApiResources), include: [ fields.includes('claims') && { - model: this.models.ApiClaims, + model: ApiClaims, attributes: ['id', 'type'], as: 'ApiClaims', required: false, }, fields.includes('scopes') && { - model: this.models.ApiScopes, + model: ApiScopes, attributes: ['id', 'name', 'emphasize', 'required', 'showInDiscoveryDocument'], as: 'ApiScopes', required: false, }, fields.includes('secrets') && { - model: this.models.ApiSecrets, + model: ApiSecrets, attributes: ['id', 'type', 'value', 'expiresAt'], as: 'ApiSecrets', required: false, }, fields.includes('properties') && { - model: this.models.ApiProperties, + model: ApiProperties, attributes: ['id', 'key', 'value'], as: 'ApiProperties', required: false, @@ -140,15 +139,15 @@ export class ApiResourceDataSource extends BaseDataSource { where.enabled = query.enabled; } - return this.models.ApiResources.findAndCountAll({ - attributes: this.filterFields(fields, this.models.ApiResources), + return ApiResources.findAndCountAll({ + attributes: this.filterFields(fields, ApiResources), where, offset, limit, order: [ // 根据 keyword 匹配程度排序 !!query.keyword && [ - this.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 + this.datasourceService.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 WHEN ${keywordField} LIKE '${query.keyword}%' THEN 1 WHEN ${keywordField} LIKE '%${query.keyword}%' THEN 2 WHEN ${keywordField} LIKE '%${query.keyword}' THEN 3 @@ -168,7 +167,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input new resource input */ async create(input: NewApiResourceInput): Promise { - const exists = await this.models.ApiResources.count({ + const exists = await ApiResources.count({ where: { name: input.name, }, @@ -182,7 +181,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - return this.models.ApiResources.create(input).then((api) => api.toJSON()); + return ApiResources.create(input).then((api) => api.toJSON()); } /** @@ -191,7 +190,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input update resource input */ async update(id: number, input: UpdateApiResourceInput): Promise { - const nonEditable = await this.models.ApiResources.count({ + const nonEditable = await ApiResources.count({ where: { id, nonEditable: true, @@ -206,7 +205,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - await this.models.ApiResources.update(input, { + await ApiResources.update(input, { where: { id, }, @@ -218,7 +217,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api resource id */ async updateLastAccessed(id: number): Promise { - await this.models.ApiResources.update( + await ApiResources.update( { lastAccessed: new Date(), }, @@ -235,7 +234,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api resource id */ async delete(id: number): Promise { - const resource = await this.models.ApiResources.findByPk(id); + const resource = await ApiResources.findByPk(id); if (resource) { if (resource.nonEditable) throw new ValidationError( @@ -267,16 +266,16 @@ export class ApiResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.ApiResources.findByPk(id, { + return ApiResources.findByPk(id, { attributes: ['id', 'name', 'displayName', 'nonEditable'], include: [ { - model: this.models.ApiClaims, - attributes: this.filterFields(fields, this.models.ApiClaims), + model: ApiClaims, + attributes: this.filterFields(fields, ApiClaims), as: 'ApiClaims', }, ], - order: [[{ model: this.models.ApiClaims, as: 'ApiClaims' }, orderField, order]], + order: [[{ model: ApiClaims, as: 'ApiClaims' }, orderField, order]], }).then((api) => { if (!api) return; @@ -294,14 +293,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input new api claim input */ createClaim(apiResourceId: number, input: NewApiClaimInput): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.ApiClaims.count({ + const exists = await ApiClaims.count({ where: { apiResourceId, type: input.type, @@ -316,7 +315,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - return this.models.ApiClaims.create({ + return ApiClaims.create({ ...input, apiResourceId, }).then((claim) => claim.toJSON()); @@ -329,14 +328,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param inputs new api resource claims input */ createClaims(apiResourceId: number, inputs: NewApiClaimInput[]): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then(async (count) => { if (count === 0) return []; - const claims = await this.models.ApiClaims.findAll({ + const claims = await ApiClaims.findAll({ attributes: ['type'], where: { apiResourceId, @@ -348,7 +347,7 @@ export class ApiResourceDataSource extends BaseDataSource { const existsType = claims.map((claim) => claim.type); - return this.models.ApiClaims.bulkCreate( + return ApiClaims.bulkCreate( inputs .filter((input) => !existsType.includes(input.type)) .map((input) => ({ @@ -364,7 +363,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api claim id */ async deleteClaim(id: number): Promise { - await this.models.ApiClaims.destroy({ + await ApiClaims.destroy({ where: { id, }, @@ -385,11 +384,11 @@ export class ApiResourceDataSource extends BaseDataSource { }) | undefined > { - return this.models.ApiScopes.findByPk(id, { - attributes: this.filterFields(fields, this.models.ApiScopes), + return ApiScopes.findByPk(id, { + attributes: this.filterFields(fields, ApiScopes), include: [ fields.includes('claims') && { - model: this.models.ApiScopeClaims, + model: ApiScopeClaims, attributes: ['id', 'type'], as: 'ApiScopeClaims', required: false, @@ -431,15 +430,15 @@ export class ApiResourceDataSource extends BaseDataSource { where.apiResourceId = query.apiResourceId; } - return this.models.ApiScopes.findAndCountAll({ - attributes: this.filterFields(fields, this.models.ApiScopes), + return ApiScopes.findAndCountAll({ + attributes: this.filterFields(fields, ApiScopes), where, offset, limit, order: [ // 根据 keyword 匹配程度排序 !!query.keyword && [ - this.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 + this.datasourceService.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 WHEN ${keywordField} LIKE '${query.keyword}%' THEN 1 WHEN ${keywordField} LIKE '%${query.keyword}%' THEN 2 WHEN ${keywordField} LIKE '%${query.keyword}' THEN 3 @@ -469,11 +468,11 @@ export class ApiResourceDataSource extends BaseDataSource { } > > { - return this.models.ApiScopes.findAll({ - attributes: this.filterFields(fields, this.models.ApiScopes), + return ApiScopes.findAll({ + attributes: this.filterFields(fields, ApiScopes), include: [ fields.includes('claims') && { - model: this.models.ApiScopeClaims, + model: ApiScopeClaims, attributes: ['id', 'type'], as: 'ApiScopeClaims', required: false, @@ -502,14 +501,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @returns */ createScope(apiResourceId: number, input: NewApiScopeInput): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.ApiScopes.count({ + const exists = await ApiScopes.count({ where: { apiResourceId, name: input.name, @@ -524,7 +523,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - return this.models.ApiScopes.create({ + return ApiScopes.create({ ...input, apiResourceId, }).then((scope) => scope.toJSON()); @@ -537,10 +536,10 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input api scope input */ async updateScope(id: number, input: UpdateApiScopeInput): Promise { - const nonEditable = await this.models.ApiResources.count({ + const nonEditable = await ApiResources.count({ include: [ { - model: this.models.ApiScopes, + model: ApiScopes, as: 'ApiScopes', where: { id, @@ -560,7 +559,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - await this.models.ApiScopes.update(input, { + await ApiScopes.update(input, { where: { id, }, @@ -572,10 +571,10 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api scope id */ async deleteScope(id: number): Promise { - const nonEditable = await this.models.ApiResources.count({ + const nonEditable = await ApiResources.count({ include: [ { - model: this.models.ApiScopes, + model: ApiScopes, as: 'ApiScopes', where: { id, @@ -595,7 +594,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - await this.models.ApiScopes.destroy({ + await ApiScopes.destroy({ where: { id, }, @@ -612,16 +611,16 @@ export class ApiResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.ApiScopes.findByPk(apiScopeId, { + return ApiScopes.findByPk(apiScopeId, { attributes: ['id', 'apiResourceId', 'name', 'displayName'], include: [ { - model: this.models.ApiScopeClaims, - attributes: this.filterFields(fields, this.models.ApiScopeClaims), + model: ApiScopeClaims, + attributes: this.filterFields(fields, ApiScopeClaims), as: 'ApiScopeClaims', }, ], - order: [[{ model: this.models.ApiScopeClaims, as: 'ApiScopeClaims' }, orderField, order]], + order: [[{ model: ApiScopeClaims, as: 'ApiScopeClaims' }, orderField, order]], }).then((api) => { if (!api) return; @@ -641,14 +640,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input new api scope claim input */ createScopeClaim(apiScopeId: number, input: NewApiScopeClaimInput): Promise { - return this.models.ApiScopes.count({ + return ApiScopes.count({ where: { id: apiScopeId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.ApiScopeClaims.count({ + const exists = await ApiScopeClaims.count({ where: { apiScopeId, type: input.type, @@ -663,7 +662,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - return this.models.ApiScopeClaims.create({ + return ApiScopeClaims.create({ ...input, apiScopeId, }).then((claim) => claim.toJSON()); @@ -676,14 +675,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param inputs new api scope claims input */ createScopeClaims(apiScopeId: number, inputs: NewApiScopeClaimInput[]): Promise { - return this.models.ApiScopes.count({ + return ApiScopes.count({ where: { id: apiScopeId, }, }).then(async (count) => { if (count === 0) return []; - const claims = await this.models.ApiScopeClaims.findAll({ + const claims = await ApiScopeClaims.findAll({ attributes: ['type'], where: { apiScopeId, @@ -695,7 +694,7 @@ export class ApiResourceDataSource extends BaseDataSource { const existsType = claims.map((claim) => claim.type); - return this.models.ApiScopeClaims.bulkCreate( + return ApiScopeClaims.bulkCreate( inputs .filter((input) => !existsType.includes(input.type)) .map((input) => ({ @@ -711,7 +710,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api scope claim id */ async deleteScopeClaim(id: number): Promise { - await this.models.ApiScopeClaims.destroy({ + await ApiScopeClaims.destroy({ where: { id, }, @@ -729,16 +728,16 @@ export class ApiResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'DESC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.ApiResources.findByPk(apiResourceId, { + return ApiResources.findByPk(apiResourceId, { attributes: ['id', 'name', 'displayName', 'nonEditable'], include: [ { - model: this.models.ApiSecrets, - attributes: this.filterFields(fields, this.models.ApiSecrets), + model: ApiSecrets, + attributes: this.filterFields(fields, ApiSecrets), as: 'ApiSecrets', }, ], - order: [[{ model: this.models.ApiSecrets, as: 'ApiSecrets' }, orderField, order]], + order: [[{ model: ApiSecrets, as: 'ApiSecrets' }, orderField, order]], }).then((api) => { if (!api) return; @@ -756,14 +755,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input new api secret input */ createSecret(apiResourceId: number, input: NewApiSecretInput): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then((count) => { if (count === 0) return; - return this.models.ApiSecrets.create({ + return ApiSecrets.create({ ...input, apiResourceId, }).then((secret) => secret.toJSON()); @@ -775,7 +774,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api secret id */ async deleteSecret(id: number): Promise { - await this.models.ApiSecrets.destroy({ + await ApiSecrets.destroy({ where: { id, }, @@ -793,16 +792,16 @@ export class ApiResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'DESC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.ApiResources.findByPk(apiResourceId, { + return ApiResources.findByPk(apiResourceId, { attributes: ['id', 'name', 'displayName', 'nonEditable'], include: [ { - model: this.models.ApiProperties, - attributes: this.filterFields(fields, this.models.ApiProperties), + model: ApiProperties, + attributes: this.filterFields(fields, ApiProperties), as: 'ApiProperties', }, ], - order: [[{ model: this.models.ApiProperties, as: 'ApiProperties' }, orderField, order]], + order: [[{ model: ApiProperties, as: 'ApiProperties' }, orderField, order]], }).then((api) => { if (!api) return; @@ -822,14 +821,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param input api property input */ createProperty(apiResourceId: number, input: NewApiPropertyInput): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.ApiProperties.count({ + const exists = await ApiProperties.count({ where: { apiResourceId, key: input.key, @@ -844,7 +843,7 @@ export class ApiResourceDataSource extends BaseDataSource { ), ); - return this.models.ApiProperties.create({ + return ApiProperties.create({ ...input, apiResourceId, }).then((property) => property.toJSON()); @@ -857,14 +856,14 @@ export class ApiResourceDataSource extends BaseDataSource { * @param inputs new api resource properties input */ createProperties(apiResourceId: number, inputs: NewApiPropertyInput[]): Promise { - return this.models.ApiResources.count({ + return ApiResources.count({ where: { id: apiResourceId, }, }).then(async (count) => { if (count === 0) return []; - const properties = await this.models.ApiProperties.findAll({ + const properties = await ApiProperties.findAll({ attributes: ['key'], where: { apiResourceId, @@ -876,7 +875,7 @@ export class ApiResourceDataSource extends BaseDataSource { const existsKey = properties.map((property) => property.key); - return this.models.ApiProperties.bulkCreate( + return ApiProperties.bulkCreate( inputs .filter((input) => !existsKey.includes(input.key)) .map((input) => ({ @@ -892,7 +891,7 @@ export class ApiResourceDataSource extends BaseDataSource { * @param id api property id */ async deleteProperty(id: number): Promise { - await this.models.ApiProperties.destroy({ + await ApiProperties.destroy({ where: { id, }, diff --git a/servers/identity-server/src/datasource/sequelize/datasources/base.datasource.ts b/servers/identity-server/src/datasource/sequelize/datasources/base.datasource.ts index 5c50cf8d..e05581e0 100644 --- a/servers/identity-server/src/datasource/sequelize/datasources/base.datasource.ts +++ b/servers/identity-server/src/datasource/sequelize/datasources/base.datasource.ts @@ -1,96 +1,33 @@ import { ModelDefined, ModelStatic, ProjectionAlias, Dialect } from 'sequelize'; -import { ModuleRef } from '@nestjs/core'; -import { Logger, OnModuleInit } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { IdentityDatasourceService } from '../../datasource.service'; -import { IdentityDatasourceOptions } from '../../interfaces/identity-datasource-options.interface'; -import { IDENTITY_OPTIONS } from '../../constants'; -export abstract class BaseDataSource implements OnModuleInit { - protected readonly logger!: Logger; - protected readonly moduleRef?: ModuleRef; - protected datasourceService?: IdentityDatasourceService; - protected datasourceOptions?: IdentityDatasourceOptions; +export abstract class BaseDataSource { + protected readonly logger: Logger; - constructor() { + constructor(protected readonly datasourceService: IdentityDatasourceService) { this.logger = new Logger(this.constructor.name, { timestamp: true }); } - onModuleInit() { - !this.datasourceService && - (this.datasourceService = this.moduleRef?.get(IdentityDatasourceService, { strict: false })); - !this.datasourceOptions && - (this.datasourceOptions = this.moduleRef?.get(IDENTITY_OPTIONS, { strict: false })); - } - - private ensureIdentityService() { - if (!this.datasourceService) { - this.logger.warn('Please inject IdentityService or ModuleRef in SubClass constructor'); - throw new Error('IdentityService not initialized'); - } - } - - private ensureIdentityOptions() { - if (!this.datasourceOptions) { - this.logger.warn('Please inject IdentityOptions or ModuleRef in SubClass constructor'); - throw new Error('IdentityOptions not initialized'); - } - } - /** - * Get dialect from identityOptions.connection - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Get dialect */ - protected get databaseDialect() { - this.ensureIdentityOptions(); - - return typeof this.datasourceOptions!.connection === 'string' - ? (this.datasourceOptions!.connection.split(':')[0] as Dialect) - : this.datasourceOptions!.connection.dialect ?? 'mysql'; + protected get databaseDialect(): Dialect { + return this.datasourceService.sequelize.getDialect() as Dialect; } /** - * Shortcut for this.identityOptions.tablePrefix - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Get table prefix */ protected get tablePrefix() { - this.ensureIdentityOptions(); - - return this.datasourceOptions!.tablePrefix || ''; + return this.datasourceService.tablePrefix; } /** - * Shortcut for this.identityOptions.translate - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Translate function */ protected get translate() { - this.ensureIdentityOptions(); - - return this.datasourceOptions!.translate || ((key: string, fallback: string) => fallback); - } - - /** - * Shortcut for this.identityService.sequelize - * Inject IdentityService in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit - */ - protected get sequelize() { - this.ensureIdentityService(); - - return this.datasourceService!.sequelize; - } - - /** - * Shortcut for this.identityService.models - * Inject IdentityService in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit - */ - protected get models() { - this.ensureIdentityService(); - - return this.datasourceService!.models; + return this.datasourceService.translate; } /** @@ -128,6 +65,6 @@ export abstract class BaseDataSource implements OnModuleInit { model: ModelDefined, modelName?: string, ) { - return this.sequelize.col(`${modelName || model.name}.${String(this.field(fieldName, model))}`); + return this.datasourceService.sequelize.col(`${modelName || model.name}.${String(this.field(fieldName, model))}`); } } diff --git a/servers/identity-server/src/datasource/sequelize/datasources/client.datasource.ts b/servers/identity-server/src/datasource/sequelize/datasources/client.datasource.ts index f5168c20..7746ce6f 100644 --- a/servers/identity-server/src/datasource/sequelize/datasources/client.datasource.ts +++ b/servers/identity-server/src/datasource/sequelize/datasources/client.datasource.ts @@ -1,7 +1,18 @@ -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ValidationError } from '@ace-pomelo/shared/server'; import { Includeable, Op, WhereOptions } from 'sequelize'; +import { IdentityDatasourceService } from '../../datasource.service'; +import { + Clients, + ClientCorsOrigins, + ClientClaims, + ClientGrantTypes, + ClientScopes, + ClientRedirectUris, + ClientPostLogoutRedirectUris, + ClientSecrets, + ClientProperties, +} from '../entities'; import { ClientModel, PagedClientArgs, @@ -37,8 +48,8 @@ import { BaseDataSource } from './base.datasource'; @Injectable() export class ClientDataSource extends BaseDataSource { - constructor(protected readonly moduleRef: ModuleRef) { - super(); + constructor(datasourceService: IdentityDatasourceService) { + super(datasourceService); } /** @@ -67,56 +78,56 @@ export class ClientDataSource extends BaseDataSource { fields.unshift('id'); } - return this.models.Clients.findOne({ - attributes: this.filterFields(fields, this.models.Clients), + return Clients.findOne({ + attributes: this.filterFields(fields, Clients), where: { clientId, }, include: [ fields.includes('corsOrigins') && { - model: this.models.ClientCorsOrigins, + model: ClientCorsOrigins, attributes: ['id', 'origin'], as: 'ClientCorsOrigins', required: false, }, fields.includes('claims') && { - model: this.models.ClientClaims, + model: ClientClaims, attributes: ['id', 'type', 'value'], as: 'ClientClaims', required: false, }, fields.includes('grantTypes') && { - model: this.models.ClientGrantTypes, + model: ClientGrantTypes, attributes: ['id', 'grantType'], as: 'ClientGrantTypes', required: false, }, fields.includes('scopes') && { - model: this.models.ClientScopes, + model: ClientScopes, attributes: ['id', 'scope'], as: 'ClientScopes', required: false, }, fields.includes('redirectUris') && { - model: this.models.ClientRedirectUris, + model: ClientRedirectUris, attributes: ['id', 'redirectUri'], as: 'ClientRedirectUris', required: false, }, fields.includes('postLogoutRedirectUris') && { - model: this.models.ClientPostLogoutRedirectUris, + model: ClientPostLogoutRedirectUris, attributes: ['id', 'postLogoutRedirectUri'], as: 'ClientPostLogoutRedirectUris', required: false, }, fields.includes('secrets') && { - model: this.models.ClientSecrets, + model: ClientSecrets, attributes: ['id', 'type', 'value', 'expiresAt'], as: 'ClientSecrets', required: false, }, fields.includes('properties') && { - model: this.models.ClientProperties, + model: ClientProperties, attributes: ['id', 'key', 'value'], as: 'ClientProperties', required: false, @@ -186,8 +197,8 @@ export class ClientDataSource extends BaseDataSource { }; } - return this.models.Clients.findAndCountAll({ - attributes: this.filterFields(fields, this.models.Clients), + return Clients.findAndCountAll({ + attributes: this.filterFields(fields, Clients), where, offset, limit, @@ -203,7 +214,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client input */ create(input: NewClientInput): Promise { - return this.models.Clients.create(input).then((client) => client.toJSON()); + return Clients.create(input).then((client) => client.toJSON()); } /** @@ -212,7 +223,7 @@ export class ClientDataSource extends BaseDataSource { * @param input update client input */ async update(clientId: string, input: UpdateClientInput): Promise { - await this.models.Clients.update(input, { + await Clients.update(input, { where: { clientId, }, @@ -230,19 +241,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientClaims, - attributes: this.filterFields(fields, this.models.ClientClaims), + model: ClientClaims, + attributes: this.filterFields(fields, ClientClaims), as: 'ClientClaims', }, ], - order: [[{ model: this.models.ClientClaims, as: 'ClientClaims' }, orderField, order]], + order: [[{ model: ClientClaims, as: 'ClientClaims' }, orderField, order]], }).then((client) => { if (!client) return; @@ -260,7 +271,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client claim input */ createClaim(clientId: string, input: NewClientClaimInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -268,7 +279,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientClaims.count({ + const exists = await ClientClaims.count({ where: { clientId: client.id, type: input.type, @@ -280,7 +291,7 @@ export class ClientDataSource extends BaseDataSource { this.translate('identity-server.datasource.client.claim_has_existed', 'Client claim has already existed!'), ); - return this.models.ClientClaims.create({ + return ClientClaims.create({ ...input, clientId: client.id, }).then((claim) => claim.toJSON()); @@ -293,7 +304,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client claims input */ createClaims(clientId: string, inputs: NewClientClaimInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -301,7 +312,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const claims = await this.models.ClientClaims.findAll({ + const claims = await ClientClaims.findAll({ attributes: ['type'], where: { clientId: client.id, @@ -313,7 +324,7 @@ export class ClientDataSource extends BaseDataSource { const existsType = claims.map((claim) => claim.type); - return this.models.ClientClaims.bulkCreate( + return ClientClaims.bulkCreate( inputs .filter((input) => !existsType.includes(input.type)) .map((input) => ({ @@ -329,7 +340,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client claim id */ async deleteClaim(id: number): Promise { - await this.models.ClientClaims.destroy({ + await ClientClaims.destroy({ where: { id, }, @@ -347,19 +358,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientCorsOrigins, - attributes: this.filterFields(fields, this.models.ClientCorsOrigins), + model: ClientCorsOrigins, + attributes: this.filterFields(fields, ClientCorsOrigins), as: 'ClientCorsOrigins', }, ], - order: [[{ model: this.models.ClientCorsOrigins, as: 'ClientCorsOrigins' }, orderField, order]], + order: [[{ model: ClientCorsOrigins, as: 'ClientCorsOrigins' }, orderField, order]], }).then((client) => { if (!client) return; @@ -379,7 +390,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client cors origin input */ createCorsOrigin(clientId: string, input: NewClientCorsOriginInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -387,7 +398,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientCorsOrigins.count({ + const exists = await ClientCorsOrigins.count({ where: { clientId: client.id, origin: input.origin, @@ -402,7 +413,7 @@ export class ClientDataSource extends BaseDataSource { ), ); - return this.models.ClientCorsOrigins.create({ + return ClientCorsOrigins.create({ ...input, clientId: client.id, }).then((corsOrigin) => corsOrigin.toJSON()); @@ -415,7 +426,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client cors origins input */ createCorsOrigins(clientId: string, inputs: NewClientCorsOriginInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -423,7 +434,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const corsOrigins = await this.models.ClientCorsOrigins.findAll({ + const corsOrigins = await ClientCorsOrigins.findAll({ attributes: ['origin'], where: { clientId: client.id, @@ -435,7 +446,7 @@ export class ClientDataSource extends BaseDataSource { const existsOrigin = corsOrigins.map((corsOrigin) => corsOrigin.origin); - return this.models.ClientCorsOrigins.bulkCreate( + return ClientCorsOrigins.bulkCreate( inputs .filter((input) => !existsOrigin.includes(input.origin)) .map((input) => ({ @@ -451,7 +462,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client cors origin id */ async deleteCorsOrigin(id: number): Promise { - await this.models.ClientCorsOrigins.destroy({ + await ClientCorsOrigins.destroy({ where: { id, }, @@ -469,19 +480,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientScopes, - attributes: this.filterFields(fields, this.models.ClientScopes), + model: ClientScopes, + attributes: this.filterFields(fields, ClientScopes), as: 'ClientScopes', }, ], - order: [[{ model: this.models.ClientScopes, as: 'ClientScopes' }, orderField, order]], + order: [[{ model: ClientScopes, as: 'ClientScopes' }, orderField, order]], }).then((client) => { if (!client) return; @@ -499,7 +510,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client scope input */ createScope(clientId: string, input: NewClientScopeInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -507,7 +518,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientScopes.count({ + const exists = await ClientScopes.count({ where: { clientId: client.id, scope: input.scope, @@ -519,7 +530,7 @@ export class ClientDataSource extends BaseDataSource { this.translate('identity-server.datasource.client.scope_has_existed', 'Client scope has already existed!'), ); - return this.models.ClientScopes.create({ + return ClientScopes.create({ ...input, clientId: client.id, }).then((scope) => scope.toJSON()); @@ -532,7 +543,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client scopes input */ createScopes(clientId: string, inputs: NewClientScopeInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -540,7 +551,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const scopes = await this.models.ClientScopes.findAll({ + const scopes = await ClientScopes.findAll({ attributes: ['scope'], where: { clientId: client.id, @@ -552,7 +563,7 @@ export class ClientDataSource extends BaseDataSource { const existsScope = scopes.map((scope) => scope.scope); - return this.models.ClientScopes.bulkCreate( + return ClientScopes.bulkCreate( inputs .filter((input) => !existsScope.includes(input.scope)) .map((input) => ({ @@ -568,7 +579,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client scope id */ async deleteScope(id: number): Promise { - await this.models.ClientScopes.destroy({ + await ClientScopes.destroy({ where: { id, }, @@ -586,19 +597,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientGrantTypes, - attributes: this.filterFields(fields, this.models.ClientGrantTypes), + model: ClientGrantTypes, + attributes: this.filterFields(fields, ClientGrantTypes), as: 'ClientGrantTypes', }, ], - order: [[{ model: this.models.ClientGrantTypes, as: 'ClientGrantTypes' }, orderField, order]], + order: [[{ model: ClientGrantTypes, as: 'ClientGrantTypes' }, orderField, order]], }).then((client) => { if (!client) return; @@ -618,7 +629,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client grant type input */ createGrantType(clientId: string, input: NewClientGrantTypeInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -626,7 +637,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientGrantTypes.count({ + const exists = await ClientGrantTypes.count({ where: { clientId: client.id, grantType: input.grantType, @@ -641,7 +652,7 @@ export class ClientDataSource extends BaseDataSource { ), ); - return this.models.ClientGrantTypes.create({ + return ClientGrantTypes.create({ ...input, clientId: client.id, }).then((grantType) => grantType.toJSON()); @@ -654,7 +665,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client grant types input */ createGrantTypes(clientId: string, inputs: NewClientGrantTypeInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -662,7 +673,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const grantTypes = await this.models.ClientGrantTypes.findAll({ + const grantTypes = await ClientGrantTypes.findAll({ attributes: ['grantType'], where: { clientId: client.id, @@ -674,7 +685,7 @@ export class ClientDataSource extends BaseDataSource { const existsGrantType = grantTypes.map((grantType) => grantType.grantType); - return this.models.ClientGrantTypes.bulkCreate( + return ClientGrantTypes.bulkCreate( inputs .filter((input) => !existsGrantType.includes(input.grantType)) .map((input) => ({ @@ -690,7 +701,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client grant type id */ async deleteGrantType(id: number): Promise { - await this.models.ClientGrantTypes.destroy({ + await ClientGrantTypes.destroy({ where: { id, }, @@ -708,19 +719,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientRedirectUris, - attributes: this.filterFields(fields, this.models.ClientRedirectUris), + model: ClientRedirectUris, + attributes: this.filterFields(fields, ClientRedirectUris), as: 'ClientRedirectUris', }, ], - order: [[{ model: this.models.ClientRedirectUris, as: 'ClientRedirectUris' }, orderField, order]], + order: [[{ model: ClientRedirectUris, as: 'ClientRedirectUris' }, orderField, order]], }).then((client) => { if (!client) return; @@ -740,7 +751,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client redirect uri input */ createRedirectUri(clientId: string, input: NewClientRedirectUriInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -748,7 +759,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientRedirectUris.count({ + const exists = await ClientRedirectUris.count({ where: { clientId: client.id, redirectUri: input.redirectUri, @@ -763,7 +774,7 @@ export class ClientDataSource extends BaseDataSource { ), ); - return this.models.ClientRedirectUris.create({ + return ClientRedirectUris.create({ ...input, clientId: client.id, }).then((redirectUri) => redirectUri.toJSON()); @@ -776,7 +787,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client redirect uris input */ createRedirectUris(clientId: string, inputs: NewClientRedirectUriInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -784,7 +795,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const redirectUris = await this.models.ClientRedirectUris.findAll({ + const redirectUris = await ClientRedirectUris.findAll({ attributes: ['redirectUri'], where: { clientId: client.id, @@ -796,7 +807,7 @@ export class ClientDataSource extends BaseDataSource { const existsRedirectUri = redirectUris.map((redirectUri) => redirectUri.redirectUri); - return this.models.ClientRedirectUris.bulkCreate( + return ClientRedirectUris.bulkCreate( inputs .filter((input) => !existsRedirectUri.includes(input.redirectUri)) .map((input) => ({ @@ -812,7 +823,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client redirect uri id */ async deleteRedirectUri(id: number): Promise { - await this.models.ClientRedirectUris.destroy({ + await ClientRedirectUris.destroy({ where: { id, }, @@ -830,21 +841,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientPostLogoutRedirectUris, - attributes: this.filterFields(fields, this.models.ClientPostLogoutRedirectUris), + model: ClientPostLogoutRedirectUris, + attributes: this.filterFields(fields, ClientPostLogoutRedirectUris), as: 'ClientPostLogoutRedirectUris', }, ], - order: [ - [{ model: this.models.ClientPostLogoutRedirectUris, as: 'ClientPostLogoutRedirectUris' }, orderField, order], - ], + order: [[{ model: ClientPostLogoutRedirectUris, as: 'ClientPostLogoutRedirectUris' }, orderField, order]], }).then((client) => { if (!client) return; @@ -867,7 +876,7 @@ export class ClientDataSource extends BaseDataSource { clientId: string, input: NewClientPostLogoutRedirectUriInput, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -875,7 +884,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientPostLogoutRedirectUris.count({ + const exists = await ClientPostLogoutRedirectUris.count({ where: { clientId: client.id, postLogoutRedirectUri: input.postLogoutRedirectUri, @@ -890,7 +899,7 @@ export class ClientDataSource extends BaseDataSource { ), ); - return this.models.ClientPostLogoutRedirectUris.create({ + return ClientPostLogoutRedirectUris.create({ ...input, clientId: client.id, }).then((postLogoutRedirectUri) => postLogoutRedirectUri.toJSON()); @@ -906,7 +915,7 @@ export class ClientDataSource extends BaseDataSource { clientId: string, inputs: NewClientPostLogoutRedirectUriInput[], ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -914,7 +923,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const postLogoutRedirectUris = await this.models.ClientPostLogoutRedirectUris.findAll({ + const postLogoutRedirectUris = await ClientPostLogoutRedirectUris.findAll({ attributes: ['postLogoutRedirectUri'], where: { clientId: client.id, @@ -928,7 +937,7 @@ export class ClientDataSource extends BaseDataSource { (postLogoutRedirectUri) => postLogoutRedirectUri.postLogoutRedirectUri, ); - return this.models.ClientPostLogoutRedirectUris.bulkCreate( + return ClientPostLogoutRedirectUris.bulkCreate( inputs .filter((input) => !existsPostLogoutRedirectUri.includes(input.postLogoutRedirectUri)) .map((input) => ({ @@ -948,7 +957,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client post logout redirect uri id */ async deletePostLogoutRedirectUri(id: number): Promise { - await this.models.ClientPostLogoutRedirectUris.destroy({ + await ClientPostLogoutRedirectUris.destroy({ where: { id, }, @@ -966,19 +975,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'DESC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientSecrets, - attributes: this.filterFields(fields, this.models.ClientSecrets), + model: ClientSecrets, + attributes: this.filterFields(fields, ClientSecrets), as: 'ClientSecrets', }, ], - order: [[{ model: this.models.ClientSecrets, as: 'ClientSecrets' }, orderField, order]], + order: [[{ model: ClientSecrets, as: 'ClientSecrets' }, orderField, order]], }).then((client) => { if (!client) return; @@ -996,7 +1005,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client secret input */ createSecret(clientId: string, input: NewClientSecretInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -1004,7 +1013,7 @@ export class ClientDataSource extends BaseDataSource { }).then((client) => { if (!client) return; - return this.models.ClientSecrets.create({ + return ClientSecrets.create({ ...input, clientId: client.id, }).then((secret) => secret.toJSON()); @@ -1016,7 +1025,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client secret id */ async deleteSecret(id: number): Promise { - await this.models.ClientSecrets.destroy({ + await ClientSecrets.destroy({ where: { id, }, @@ -1034,19 +1043,19 @@ export class ClientDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'DESC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['clientId', 'clientName'], where: { clientId, }, include: [ { - model: this.models.ClientProperties, - attributes: this.filterFields(fields, this.models.ClientProperties), + model: ClientProperties, + attributes: this.filterFields(fields, ClientProperties), as: 'ClientProperties', }, ], - order: [[{ model: this.models.ClientProperties, as: 'ClientProperties' }, orderField, order]], + order: [[{ model: ClientProperties, as: 'ClientProperties' }, orderField, order]], }).then((client) => { if (!client) return; @@ -1066,7 +1075,7 @@ export class ClientDataSource extends BaseDataSource { * @param input new client property input */ createProperty(clientId: string, input: NewClientPropertyInput): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -1074,7 +1083,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return; - const exists = await this.models.ClientProperties.count({ + const exists = await ClientProperties.count({ where: { clientId: client.id, key: input.key, @@ -1089,7 +1098,7 @@ export class ClientDataSource extends BaseDataSource { ), ); - return this.models.ClientProperties.create({ + return ClientProperties.create({ ...input, clientId: client.id, }).then((property) => property.toJSON()); @@ -1102,7 +1111,7 @@ export class ClientDataSource extends BaseDataSource { * @param inputs new client properties input */ createProperties(clientId: string, inputs: NewClientPropertyInput[]): Promise { - return this.models.Clients.findOne({ + return Clients.findOne({ attributes: ['id'], where: { clientId, @@ -1110,7 +1119,7 @@ export class ClientDataSource extends BaseDataSource { }).then(async (client) => { if (!client) return []; - const properties = await this.models.ClientProperties.findAll({ + const properties = await ClientProperties.findAll({ attributes: ['key'], where: { clientId: client.id, @@ -1122,7 +1131,7 @@ export class ClientDataSource extends BaseDataSource { const existsKey = properties.map((property) => property.key); - return this.models.ClientProperties.bulkCreate( + return ClientProperties.bulkCreate( inputs .filter((input) => !existsKey.includes(input.key)) .map((input) => ({ @@ -1138,7 +1147,7 @@ export class ClientDataSource extends BaseDataSource { * @param id client property id */ async deleteProperty(id: number): Promise { - await this.models.ClientProperties.destroy({ + await ClientProperties.destroy({ where: { id, }, diff --git a/servers/identity-server/src/datasource/sequelize/datasources/identity-resource.datasource.ts b/servers/identity-server/src/datasource/sequelize/datasources/identity-resource.datasource.ts index c24c1c6e..0c970fac 100644 --- a/servers/identity-server/src/datasource/sequelize/datasources/identity-resource.datasource.ts +++ b/servers/identity-server/src/datasource/sequelize/datasources/identity-resource.datasource.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; import { ValidationError } from '@ace-pomelo/shared/server'; import { WhereOptions, Order, Attributes, Op, Includeable } from 'sequelize'; -import { default as IdentityResources } from '../entities/identity-resources.entity'; +import { IdentityDatasourceService } from '../../datasource.service'; +import { IdentityResources, IdentityClaims, IdentityProperties } from '../entities'; import { IdentityResourceModel, PagedIdentityResourceArgs, @@ -15,13 +16,13 @@ import { NewIdentityPropertyInput, UpdateIdentityResourceInput, } from '../interfaces/identity-resource.interface'; -import { IdentityDatasourceService } from '../../datasource.service'; + import { BaseDataSource } from './base.datasource'; @Injectable() export class IdentityResourceDataSource extends BaseDataSource { - constructor(protected readonly datasourceService: IdentityDatasourceService) { - super(); + constructor(datasourceService: IdentityDatasourceService) { + super(datasourceService); } /** @@ -40,17 +41,17 @@ export class IdentityResourceDataSource extends BaseDataSource { fields.unshift('id'); } - return this.models.IdentityResources.findByPk(id, { - attributes: this.filterFields(fields, this.models.IdentityResources), + return IdentityResources.findByPk(id, { + attributes: this.filterFields(fields, IdentityResources), include: [ fields.includes('claims') && { - model: this.models.IdentityClaims, + model: IdentityClaims, attributes: ['id', 'type'], as: 'IdentityClaims', required: false, }, fields.includes('properties') && { - model: this.models.IdentityProperties, + model: IdentityProperties, attributes: ['id', 'key', 'value'], as: 'IdentityProperties', required: false, @@ -107,15 +108,15 @@ export class IdentityResourceDataSource extends BaseDataSource { where.enabled = query.enabled; } - return this.models.IdentityResources.findAndCountAll({ - attributes: this.filterFields(fields, this.models.IdentityResources), + return IdentityResources.findAndCountAll({ + attributes: this.filterFields(fields, IdentityResources), where, offset, limit, order: [ // 根据 keyword 匹配程度排序 !!query.keyword && [ - this.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 + this.datasourceService.sequelize.literal(`CASE WHEN ${keywordField} = '${query.keyword}' THEN 0 WHEN ${keywordField} LIKE '${query.keyword}%' THEN 1 WHEN ${keywordField} LIKE '%${query.keyword}%' THEN 2 WHEN ${keywordField} LIKE '%${query.keyword}' THEN 3 @@ -137,17 +138,17 @@ export class IdentityResourceDataSource extends BaseDataSource { getList( fields: string[], ): Promise> { - return this.models.IdentityResources.findAll({ - attributes: this.filterFields(fields!, this.models.IdentityResources), + return IdentityResources.findAll({ + attributes: this.filterFields(fields!, IdentityResources), include: [ fields.includes('claims') && { - model: this.models.IdentityClaims, + model: IdentityClaims, attributes: ['id', 'type'], as: 'IdentityClaims', required: false, }, fields.includes('properties') && { - model: this.models.IdentityProperties, + model: IdentityProperties, attributes: ['id', 'key', 'value'], as: 'IdentityProperties', required: false, @@ -182,7 +183,7 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param input resource input */ async create(input: NewIdentityResourceInput): Promise { - const exists = await this.models.IdentityResources.count({ + const exists = await IdentityResources.count({ where: { name: input.name, }, @@ -196,7 +197,7 @@ export class IdentityResourceDataSource extends BaseDataSource { ), ); - return this.models.IdentityResources.create(input).then((resource) => { + return IdentityResources.create(input).then((resource) => { return resource.toJSON(); }); } @@ -207,7 +208,7 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param input resource input */ async update(id: number, input: UpdateIdentityResourceInput): Promise { - const nonEditable = await this.models.IdentityResources.count({ + const nonEditable = await IdentityResources.count({ where: { id, nonEditable: true, @@ -222,7 +223,7 @@ export class IdentityResourceDataSource extends BaseDataSource { ), ); - await this.models.IdentityResources.update(input, { + await IdentityResources.update(input, { where: { id, }, @@ -235,7 +236,7 @@ export class IdentityResourceDataSource extends BaseDataSource { * @returns */ async delete(id: number): Promise { - const resource = await this.models.IdentityResources.findByPk(id, { + const resource = await IdentityResources.findByPk(id, { attributes: ['id', 'nonEditable'], }); @@ -270,16 +271,16 @@ export class IdentityResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'ASC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.IdentityResources.findByPk(identityResourceId, { + return IdentityResources.findByPk(identityResourceId, { attributes: ['id', 'name', 'displayName', 'nonEditable'], include: [ { - model: this.models.IdentityClaims, - attributes: this.filterFields(fields, this.models.IdentityClaims), + model: IdentityClaims, + attributes: this.filterFields(fields, IdentityClaims), as: 'IdentityClaims', }, ], - order: [[{ model: this.models.IdentityClaims, as: 'IdentityClaims' }, orderField, order]], + order: [[{ model: IdentityClaims, as: 'IdentityClaims' }, orderField, order]], }).then((resource) => { if (!resource) return; @@ -299,14 +300,14 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param input claim input */ createClaim(identityResourceId: number, input: NewIdentityClaimInput): Promise { - return this.models.IdentityResources.count({ + return IdentityResources.count({ where: { id: identityResourceId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.IdentityClaims.count({ + const exists = await IdentityClaims.count({ where: { identityResourceId, type: input.type, @@ -321,7 +322,7 @@ export class IdentityResourceDataSource extends BaseDataSource { ), ); - return this.models.IdentityClaims.create({ + return IdentityClaims.create({ ...input, identityResourceId, }).then((claim) => { @@ -336,14 +337,14 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param inputs new identity resource claims input */ createClaims(identityResourceId: number, inputs: NewIdentityClaimInput[]): Promise { - return this.models.IdentityResources.count({ + return IdentityResources.count({ where: { id: identityResourceId, }, }).then(async (count) => { if (count === 0) return []; - const claims = await this.models.IdentityClaims.findAll({ + const claims = await IdentityClaims.findAll({ attributes: ['type'], where: { identityResourceId, @@ -355,7 +356,7 @@ export class IdentityResourceDataSource extends BaseDataSource { const existsType = claims.map((claim) => claim.type); - return this.models.IdentityClaims.bulkCreate( + return IdentityClaims.bulkCreate( inputs .filter((input) => !existsType.includes(input.type)) .map((input) => ({ @@ -371,7 +372,7 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param id identity claim id */ async deleteClaim(id: number): Promise { - await this.models.IdentityClaims.destroy({ + await IdentityClaims.destroy({ where: { id, }, @@ -389,16 +390,16 @@ export class IdentityResourceDataSource extends BaseDataSource { fields: string[], { field: orderField = 'id', order = 'DESC' }: { field?: string; order?: 'ASC' | 'DESC' } = {}, ): Promise { - return this.models.IdentityResources.findByPk(identityResourceId, { + return IdentityResources.findByPk(identityResourceId, { attributes: ['id', 'name', 'displayName', 'nonEditable'], include: [ { - model: this.models.IdentityProperties, - attributes: this.filterFields(fields, this.models.IdentityProperties), + model: IdentityProperties, + attributes: this.filterFields(fields, IdentityProperties), as: 'IdentityProperties', }, ], - order: [[{ model: this.models.IdentityProperties, as: 'IdentityProperties' }, orderField, order]], + order: [[{ model: IdentityProperties, as: 'IdentityProperties' }, orderField, order]], }).then((resource) => { if (!resource) return; @@ -421,14 +422,14 @@ export class IdentityResourceDataSource extends BaseDataSource { identityResourceId: number, input: NewIdentityPropertyInput, ): Promise { - return this.models.IdentityResources.count({ + return IdentityResources.count({ where: { id: identityResourceId, }, }).then(async (count) => { if (count === 0) return; - const exists = await this.models.IdentityProperties.count({ + const exists = await IdentityProperties.count({ where: { identityResourceId, key: input.key, @@ -443,7 +444,7 @@ export class IdentityResourceDataSource extends BaseDataSource { ), ); - return this.models.IdentityProperties.create({ + return IdentityProperties.create({ ...input, identityResourceId, }).then((property) => { @@ -458,14 +459,14 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param inputs new identity resource properties input */ createProperties(identityResourceId: number, inputs: NewIdentityPropertyInput[]): Promise { - return this.models.IdentityResources.count({ + return IdentityResources.count({ where: { id: identityResourceId, }, }).then(async (count) => { if (count === 0) return []; - const properties = await this.models.IdentityProperties.findAll({ + const properties = await IdentityProperties.findAll({ attributes: ['key'], where: { identityResourceId, @@ -477,7 +478,7 @@ export class IdentityResourceDataSource extends BaseDataSource { const existsKey = properties.map((property) => property.key); - return this.models.IdentityProperties.bulkCreate( + return IdentityProperties.bulkCreate( inputs .filter((input) => !existsKey.includes(input.key)) .map((input) => ({ @@ -493,7 +494,7 @@ export class IdentityResourceDataSource extends BaseDataSource { * @param id property id */ async deleteProperty(id: number): Promise { - await this.models.IdentityProperties.destroy({ + await IdentityProperties.destroy({ where: { id, }, diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-claims.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-claims.entity.ts index b4b5fee5..e61d1266 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-claims.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-claims.entity.ts @@ -1,16 +1,16 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ApiClaimsAttributes, ApiClaimsCreationAttributes } from '../../entities/api-claims.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ApiClaims extends Model> { +export class ApiClaims extends Model> { public id!: number; public apiResourceId!: number; public type!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiClaims.init( +ApiClaims.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-properties.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-properties.entity.ts index 13181ff0..d9ea9a9b 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-properties.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-properties.entity.ts @@ -1,17 +1,17 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ApiPropertiesAttributes, ApiPropertiesCreationAttributes } from '../../entities/api-properties.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ApiProperties extends Model> { +export class ApiProperties extends Model> { public id!: number; public apiResourceId!: number; public key!: string; public value!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiProperties.init( +ApiProperties.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-resources.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-resources.entity.ts index eb897c5d..61cba972 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-resources.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-resources.entity.ts @@ -1,9 +1,12 @@ -import { Model, DataTypes, Optional } from 'sequelize'; +import { DataTypes, Optional } from 'sequelize'; import { ApiResourcesAttributes, ApiResourcesCreationAttributes } from '../../entities/api-resources.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { ApiClaims } from './api-claims.entity'; +import { ApiScopes } from './api-scopes.entity'; +import { ApiSecrets } from './api-secrets.entity'; +import { ApiProperties } from './api-properties.entity'; -export default class ApiResources extends Model< +export class ApiResources extends Model< Omit, Optional, 'nonEditable' | 'enabled'> > { @@ -20,9 +23,9 @@ export default class ApiResources extends Model< public readonly updatedAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiResources.init( +ApiResources.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -69,54 +72,54 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +ApiResources.associate = function associate() { // ApiResourcesrs.id <--> ApiClaims.apiResourceId - models.ApiResources.hasMany(models.ApiClaims, { + ApiResources.hasMany(ApiClaims, { foreignKey: 'apiResourceId', sourceKey: 'id', as: 'ApiClaims', constraints: false, }); - models.ApiClaims.belongsTo(models.ApiResources, { + ApiClaims.belongsTo(ApiResources, { foreignKey: 'apiResourceId', targetKey: 'id', constraints: false, }); // ApiResourcesrs.id <--> ApiProperties.apiResourceId - models.ApiResources.hasMany(models.ApiProperties, { + ApiResources.hasMany(ApiProperties, { foreignKey: 'apiResourceId', sourceKey: 'id', as: 'ApiProperties', constraints: false, }); - models.ApiProperties.belongsTo(models.ApiResources, { + ApiProperties.belongsTo(ApiResources, { foreignKey: 'apiResourceId', targetKey: 'id', constraints: false, }); // ApiResourcesrs.id <--> ApiScopes.apiResourceId - models.ApiResources.hasMany(models.ApiScopes, { + ApiResources.hasMany(ApiScopes, { foreignKey: 'apiResourceId', sourceKey: 'id', as: 'ApiScopes', constraints: false, }); - models.ApiScopes.belongsTo(models.ApiResources, { + ApiScopes.belongsTo(ApiResources, { foreignKey: 'apiResourceId', targetKey: 'id', constraints: false, }); // ApiResourcesrs.id <--> ApiSecrets.apiResourceId - models.ApiResources.hasMany(models.ApiSecrets, { + ApiResources.hasMany(ApiSecrets, { foreignKey: 'apiResourceId', sourceKey: 'id', as: 'ApiSecrets', constraints: false, }); - models.ApiSecrets.belongsTo(models.ApiResources, { + ApiSecrets.belongsTo(ApiResources, { foreignKey: 'apiResourceId', targetKey: 'id', constraints: false, diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-scope-claims.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-scope-claims.entity.ts index 476e4e71..747e0b90 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-scope-claims.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-scope-claims.entity.ts @@ -1,19 +1,16 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ApiScopeClaimsAttributes, ApiScopeClaimsCreationAttributes } from '../../entities/api-scope-claims.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ApiScopeClaims extends Model< - ApiScopeClaimsAttributes, - Omit -> { +export class ApiScopeClaims extends Model> { public id!: number; public apiScopeId!: number; public type!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiScopeClaims.init( +ApiScopeClaims.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-scopes.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-scopes.entity.ts index a83517e4..94999587 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-scopes.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-scopes.entity.ts @@ -1,9 +1,9 @@ -import { Model, DataTypes, Optional } from 'sequelize'; +import { DataTypes, Optional } from 'sequelize'; import { ApiScopesAttributes, ApiScopesCreationAttributes } from '../../entities/api-scopes.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { ApiScopeClaims } from './api-scope-claims.entity'; -export default class ApiScopes extends Model< +export class ApiScopes extends Model< ApiScopesAttributes, Optional, 'emphasize' | 'required' | 'showInDiscoveryDocument'> > { @@ -17,9 +17,9 @@ export default class ApiScopes extends Model< public showInDiscoveryDocument!: boolean; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiScopes.init( +ApiScopes.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -78,13 +78,13 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +ApiScopes.associate = function associate() { // ApiScopes.id <--> ApiScopeClaims.apiScopeId - models.ApiScopes.hasMany(models.ApiScopeClaims, { + ApiScopes.hasMany(ApiScopeClaims, { foreignKey: 'apiScopeId', sourceKey: 'id', as: 'ApiScopeClaims', constraints: false, }); - models.ApiScopeClaims.belongsTo(models.ApiScopes, { foreignKey: 'apiScopeId', targetKey: 'id', constraints: false }); + ApiScopeClaims.belongsTo(ApiScopes, { foreignKey: 'apiScopeId', targetKey: 'id', constraints: false }); }; diff --git a/servers/identity-server/src/datasource/sequelize/entities/api-secrets.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/api-secrets.entity.ts index 43e7687f..66dd3ebc 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/api-secrets.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/api-secrets.entity.ts @@ -1,8 +1,8 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ApiSecretsAttributes, ApiSecretsCreationAttributes } from '../../entities/api-secrets.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ApiSecrets extends Model< +export class ApiSecrets extends Model< Omit, Omit > { @@ -17,9 +17,9 @@ export default class ApiSecrets extends Model< public readonly createdAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ApiSecrets.init( +ApiSecrets.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-claims.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-claims.entity.ts index aef14534..7e6e7b59 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-claims.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-claims.entity.ts @@ -1,17 +1,17 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientClaimsAttributes, ClientClaimsCreationAttributes } from '../../entities/client-claims.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientClaims extends Model> { +export class ClientClaims extends Model> { public id!: number; public clientId!: number; public type!: string; public value!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientClaims.init( +ClientClaims.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-cors-origins.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-cors-origins.entity.ts index b98fc825..0d12e14a 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-cors-origins.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-cors-origins.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientCorsOriginsAttributes, ClientCorsOriginsCreationAttributes, } from '../../entities/client-cors-origins.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientCorsOrigins extends Model< +export class ClientCorsOrigins extends Model< ClientCorsOriginsAttributes, Omit > { @@ -14,9 +14,9 @@ export default class ClientCorsOrigins extends Model< public origin!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientCorsOrigins.init( +ClientCorsOrigins.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-grant-types.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-grant-types.entity.ts index 8e6f5dea..c22030f8 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-grant-types.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-grant-types.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientGrantTypesAttributes, ClientGrantTypesCreationAttributes, } from '../../entities/client-grant-types.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientGrantTypes extends Model< +export class ClientGrantTypes extends Model< ClientGrantTypesAttributes, Omit > { @@ -14,9 +14,9 @@ export default class ClientGrantTypes extends Model< public grantType!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientGrantTypes.init( +ClientGrantTypes.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-id-prestrictions.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-id-prestrictions.entity.ts index ea8c3b7e..b5e826f0 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-id-prestrictions.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-id-prestrictions.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientIdPRestrictionsAttributes, ClientIdPRestrictionsCreationAttributes, } from '../../entities/client-id-prestrictions.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientIdPRestrictions extends Model< +export class ClientIdPRestrictions extends Model< ClientIdPRestrictionsAttributes, Omit > { @@ -14,9 +14,9 @@ export default class ClientIdPRestrictions extends Model< public grantType!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientIdPRestrictions.init( +ClientIdPRestrictions.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-post-logout-redirect-uris.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-post-logout-redirect-uris.entity.ts index ae8cff66..e4a51131 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-post-logout-redirect-uris.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-post-logout-redirect-uris.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientPostLogoutRedirectUrisAttributes, ClientPostLogoutRedirectUrisCreationAttributes, } from '../../entities/client-post-logout-redirect-uris.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientPostLogoutRedirectUris extends Model< +export class ClientPostLogoutRedirectUris extends Model< ClientPostLogoutRedirectUrisAttributes, Omit > { @@ -14,9 +14,9 @@ export default class ClientPostLogoutRedirectUris extends Model< public postLogoutRedirectUri!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientPostLogoutRedirectUris.init( +ClientPostLogoutRedirectUris.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-properties.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-properties.entity.ts index a59fe428..3a7dca75 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-properties.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-properties.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientPropertiesAttributes, ClientPropertiesCreationAttributes, } from '../../entities/client-properties.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientProperties extends Model< +export class ClientProperties extends Model< ClientPropertiesAttributes, Omit > { @@ -15,9 +15,9 @@ export default class ClientProperties extends Model< public value!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientProperties.init( +ClientProperties.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-redirect-uris.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-redirect-uris.entity.ts index 6b7dc10e..612f27c9 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-redirect-uris.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-redirect-uris.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientRedirectUrisAttributes, ClientRedirectUrisCreationAttributes, } from '../../entities/client-redirect-uris.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientRedirectUris extends Model< +export class ClientRedirectUris extends Model< ClientRedirectUrisAttributes, Omit > { @@ -14,9 +14,9 @@ export default class ClientRedirectUris extends Model< public redirectUri!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientRedirectUris.init( +ClientRedirectUris.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-scopes.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-scopes.entity.ts index 6c2beea1..3fde0098 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-scopes.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-scopes.entity.ts @@ -1,16 +1,16 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientScopesAttributes, ClientScopesCreationAttributes } from '../../entities/client-scopes.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientScopes extends Model> { +export class ClientScopes extends Model> { public id!: number; public clientId!: number; public scope!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientScopes.init( +ClientScopes.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/client-secrets.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/client-secrets.entity.ts index 1bdc2b83..ef5acf13 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/client-secrets.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/client-secrets.entity.ts @@ -1,8 +1,8 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { ClientSecretsAttributes, ClientSecretsCreationAttributes } from '../../entities/client-secrets.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class ClientSecrets extends Model< +export class ClientSecrets extends Model< Omit, Omit > { @@ -17,9 +17,9 @@ export default class ClientSecrets extends Model< public readonly createdAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - ClientSecrets.init( +ClientSecrets.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/clients.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/clients.entity.ts index 114ac961..c6e65770 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/clients.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/clients.entity.ts @@ -1,9 +1,16 @@ -import { Model, DataTypes, Optional } from 'sequelize'; +import { DataTypes, Optional } from 'sequelize'; import { ClientsAttributes, ClientsCreationAttributes } from '../../entities/clients.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { ClientCorsOrigins } from './client-cors-origins.entity'; +import { ClientClaims } from './client-claims.entity'; +import { ClientScopes } from './client-scopes.entity'; +import { ClientGrantTypes } from './client-grant-types.entity'; +import { ClientRedirectUris } from './client-redirect-uris.entity'; +import { ClientPostLogoutRedirectUris } from './client-post-logout-redirect-uris.entity'; +import { ClientSecrets } from './client-secrets.entity'; +import { ClientProperties } from './client-properties.entity'; -export default class Clients extends Model< +export class Clients extends Model< Omit, Optional, 'enabled'> > { @@ -45,9 +52,9 @@ export default class Clients extends Model< public readonly updatedAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - Clients.init( +Clients.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -204,94 +211,94 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Clients.associate = function associate() { // Clients.id <--> ClientCorsOrigins.clientId - models.Clients.hasMany(models.ClientCorsOrigins, { + Clients.hasMany(ClientCorsOrigins, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientCorsOrigins', constraints: false, }); - models.ClientCorsOrigins.belongsTo(models.Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); + ClientCorsOrigins.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); // Clients.id <--> ClientClaims.clientId - models.Clients.hasMany(models.ClientClaims, { + Clients.hasMany(ClientClaims, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientClaims', constraints: false, }); - models.ClientClaims.belongsTo(models.Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); + ClientClaims.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); // Clients.id <--> ClientScopes.clientId - models.Clients.hasMany(models.ClientScopes, { + Clients.hasMany(ClientScopes, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientScopes', constraints: false, }); - models.ClientScopes.belongsTo(models.Clients, { + ClientScopes.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false, }); // Clients.id <--> ClientGrantTypes.clientId - models.Clients.hasMany(models.ClientGrantTypes, { + Clients.hasMany(ClientGrantTypes, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientGrantTypes', constraints: false, }); - models.ClientGrantTypes.belongsTo(models.Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); + ClientGrantTypes.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false }); // Clients.id <--> ClientRedirectUris.clientId - models.Clients.hasMany(models.ClientRedirectUris, { + Clients.hasMany(ClientRedirectUris, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientRedirectUris', constraints: false, }); - models.ClientRedirectUris.belongsTo(models.Clients, { + ClientRedirectUris.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false, }); // Clients.id <--> ClientPostLogoutRedirectUris.clientId - models.Clients.hasMany(models.ClientPostLogoutRedirectUris, { + Clients.hasMany(ClientPostLogoutRedirectUris, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientPostLogoutRedirectUris', constraints: false, }); - models.ClientPostLogoutRedirectUris.belongsTo(models.Clients, { + ClientPostLogoutRedirectUris.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false, }); // Clients.id <--> ClientSecrets.clientId - models.Clients.hasMany(models.ClientSecrets, { + Clients.hasMany(ClientSecrets, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientSecrets', constraints: false, }); - models.ClientSecrets.belongsTo(models.Clients, { + ClientSecrets.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false, }); // Clients.id <--> ClientProperties.clientId - models.Clients.hasMany(models.ClientProperties, { + Clients.hasMany(ClientProperties, { foreignKey: 'clientId', sourceKey: 'id', as: 'ClientProperties', constraints: false, }); - models.ClientProperties.belongsTo(models.Clients, { + ClientProperties.belongsTo(Clients, { foreignKey: 'clientId', targetKey: 'id', constraints: false, diff --git a/servers/identity-server/src/datasource/sequelize/entities/identity-claims.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/identity-claims.entity.ts index 1ac538a5..2de2bbe9 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/identity-claims.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/identity-claims.entity.ts @@ -1,19 +1,16 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { IdentityClaimsAttributes, IdentityClaimsCreationAttributes } from '../../entities/identity-claims.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class IdentityClaims extends Model< - IdentityClaimsAttributes, - Omit -> { +export class IdentityClaims extends Model> { public id!: number; public identityResourceId!: number; public type!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - IdentityClaims.init( +IdentityClaims.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/identity-properties.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/identity-properties.entity.ts index 5eabe1d0..00f1ae8d 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/identity-properties.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/identity-properties.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { IdentityPropertiesAttributes, IdentityPropertiesCreationAttributes, } from '../../entities/identity-properties.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class IdentityProperties extends Model< +export class IdentityProperties extends Model< IdentityPropertiesAttributes, Omit > { @@ -15,9 +15,9 @@ export default class IdentityProperties extends Model< public value!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - IdentityProperties.init( +IdentityProperties.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/identity-server/src/datasource/sequelize/entities/identity-resources.entity.ts b/servers/identity-server/src/datasource/sequelize/entities/identity-resources.entity.ts index d047532e..1c1b2410 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/identity-resources.entity.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/identity-resources.entity.ts @@ -1,12 +1,13 @@ -import { Model, DataTypes, Optional } from 'sequelize'; +import { DataTypes, Optional } from 'sequelize'; import { IdentityResourcesAttributes, IdentityResourcesCreationAttributes, } from '../../entities/identity-resources.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { IdentityClaims } from './identity-claims.entity'; +import { IdentityProperties } from './identity-properties.entity'; -export default class IdentityResources extends Model< +export class IdentityResources extends Model< Omit, Optional< Omit, @@ -28,9 +29,9 @@ export default class IdentityResources extends Model< public readonly updatedAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - IdentityResources.init( +IdentityResources.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -91,28 +92,28 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +IdentityResources.associate = function associate() { // IdentityResources.id <--> IdentityClaims.identityResourceId - models.IdentityResources.hasMany(models.IdentityClaims, { + IdentityResources.hasMany(IdentityClaims, { foreignKey: 'identityResourceId', sourceKey: 'id', as: 'IdentityClaims', constraints: false, }); - models.IdentityClaims.belongsTo(models.IdentityResources, { + IdentityClaims.belongsTo(IdentityResources, { foreignKey: 'identityResourceId', targetKey: 'id', constraints: false, }); // IdentityResources.id <--> IdentityProperties.identityResourceId - models.IdentityResources.hasMany(models.IdentityProperties, { + IdentityResources.hasMany(IdentityProperties, { foreignKey: 'identityResourceId', sourceKey: 'id', as: 'IdentityProperties', constraints: false, }); - models.IdentityProperties.belongsTo(models.IdentityResources, { + IdentityProperties.belongsTo(IdentityResources, { foreignKey: 'identityResourceId', targetKey: 'id', constraints: false, diff --git a/servers/identity-server/src/datasource/sequelize/entities/index.ts b/servers/identity-server/src/datasource/sequelize/entities/index.ts index efe933e2..5f85bc0c 100644 --- a/servers/identity-server/src/datasource/sequelize/entities/index.ts +++ b/servers/identity-server/src/datasource/sequelize/entities/index.ts @@ -1,19 +1,19 @@ -export * as ApiClaims from './api-claims.entity'; -export * as ApiProperties from './api-properties.entity'; -export * as ApiResources from './api-resources.entity'; -export * as ApiScopeClaims from './api-scope-claims.entity'; -export * as ApiScopes from './api-scopes.entity'; -export * as ApiSecrets from './api-secrets.entity'; -export * as ClientCorsOrigins from './client-cors-origins.entity'; -export * as ClientClaims from './client-claims.entity'; -export * as ClientScopes from './client-scopes.entity'; -export * as ClientGrantTypes from './client-grant-types.entity'; -export * as ClientRedirectUris from './client-redirect-uris.entity'; -export * as ClientPostLogoutRedirectUris from './client-post-logout-redirect-uris.entity'; -export * as ClientSecrets from './client-secrets.entity'; -export * as ClientProperties from './client-properties.entity'; -export * as ClientIdPRestrictions from './client-id-prestrictions.entity'; -export * as Clients from './clients.entity'; -export * as IdentityClaims from './identity-claims.entity'; -export * as IdentityProperties from './identity-properties.entity'; -export * as IdentityResources from './identity-resources.entity'; +export * from './api-claims.entity'; +export * from './api-properties.entity'; +export * from './api-resources.entity'; +export * from './api-scope-claims.entity'; +export * from './api-scopes.entity'; +export * from './api-secrets.entity'; +export * from './client-cors-origins.entity'; +export * from './client-claims.entity'; +export * from './client-scopes.entity'; +export * from './client-grant-types.entity'; +export * from './client-redirect-uris.entity'; +export * from './client-post-logout-redirect-uris.entity'; +export * from './client-secrets.entity'; +export * from './client-properties.entity'; +export * from './client-id-prestrictions.entity'; +export * from './clients.entity'; +export * from './identity-claims.entity'; +export * from './identity-properties.entity'; +export * from './identity-resources.entity'; diff --git a/servers/identity-server/src/datasource/sequelize/index.ts b/servers/identity-server/src/datasource/sequelize/index.ts index 95f16158..04566732 100644 --- a/servers/identity-server/src/datasource/sequelize/index.ts +++ b/servers/identity-server/src/datasource/sequelize/index.ts @@ -1,377 +1,6 @@ -import { merge } from 'lodash'; -import { Logger } from '@nestjs/common'; -import { Sequelize, Options, SyncOptions, ModelStatic, CreationAttributes } from 'sequelize'; -import * as DataSources from './datasources'; -import * as Entities from './entities'; -import { default as ApiResources } from './entities/api-resources.entity'; -import { default as ApiClaims } from './entities/api-claims.entity'; -import { default as ApiScopeClaims } from './entities/api-scope-claims.entity'; -import { default as ApiScopes } from './entities/api-scopes.entity'; -import { default as ApiSecrets } from './entities/api-secrets.entity'; -import { default as ApiProperties } from './entities/api-properties.entity'; -import { default as IdentityResources } from './entities/identity-resources.entity'; -import { default as IdentityClaims } from './entities/identity-claims.entity'; -import { default as IdentityProperties } from './entities/identity-properties.entity'; -import { default as Clients } from './entities/clients.entity'; -import { default as ClientClaims } from './entities/client-claims.entity'; -import { default as ClientCorsOrigins } from './entities/client-cors-origins.entity'; -import { default as ClientScopes } from './entities/client-scopes.entity'; -import { default as ClientGrantTypes } from './entities/client-grant-types.entity'; -import { default as ClientPostLogoutRedirectUris } from './entities/client-post-logout-redirect-uris.entity'; -import { default as ClientRedirectUris } from './entities/client-redirect-uris.entity'; -import { default as ClientSecrets } from './entities/client-secrets.entity'; -import { default as ClientProperites } from './entities/client-properties.entity'; -import { TableInitFunc } from './interfaces/table-init-func.interface'; -import { Models, TableAssociateFunc } from './interfaces/table-associate-func.interface'; -import { DataInitArgs } from './interfaces/data-init-args.interface'; - -export interface DatabaseOptions extends Options { - tablePrefix?: string; -} - -export class DatabaseManager { - private readonly logger = new Logger(DatabaseManager.name, { timestamp: true }); - private associated = false; - private readonly options: DatabaseOptions; - public readonly sequelize: Sequelize; - - constructor(options: DatabaseOptions); - constructor(uri: string, options?: DatabaseOptions); - constructor(uri: string | DatabaseOptions, options?: DatabaseOptions) { - options = typeof uri === 'string' ? options : uri; - this.options = merge(options || {}, { - define: { - freezeTableName: true, - underscored: true, - timestamps: true, - createdAt: true, - updatedAt: true, - charset: options?.define?.charset || 'utf8', - collate: options?.define?.collate || '', - }, - }); - this.sequelize = typeof uri === 'string' ? new Sequelize(uri, this.options) : new Sequelize(this.options); - } - - /** - * 创建数据库表结构及关联关系 - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @returns 数据库模型 - */ - associate(): Models { - const models: Partial = {}; - const associates: TableAssociateFunc[] = []; - - for (const key in Entities) { - const { - init, - associate, - default: model, - } = ( - Entities as Record< - string, - { - init?: TableInitFunc; - associate?: TableAssociateFunc; - default: ModelStatic; - } - > - )[key]; - init?.(this.sequelize, { prefix: this.options.tablePrefix || '' }); - associate && associates.push(associate); - model && (models[model.name as keyof Models] = model); - } - - // associate needs to be called after all models are initialized - associates.forEach((associate) => associate(models as Models)); - - this.associated = true; - - return models as Models; - } - - /** - * 初始化数据库 - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @param options 初始化参数 - * @returns true: 生成数据库成功;false: 跳过数据库生成(when 条件不满足) 否则抛出 Error - */ - async sync( - options?: SyncOptions & { when?: boolean | ((sequelize: Sequelize) => Promise) }, - ): Promise { - try { - await this.sequelize.authenticate(); - } catch (err: any) { - this.logger.error(`Unable to connect to the database, Error: ${err.message}`); - throw err; - } - - // 初始化关联关系 - !this.associated && this.associate(); - - try { - // eslint-disable-next-line prefer-const - let { when = true, ...syncOptions } = options || {}; - if (typeof when === 'function') { - when = await when.call(null, this.sequelize); - } - if (when) { - await this.sequelize.sync(syncOptions); - return true; - } - return false; - } catch (err: any) { - this.logger.error(`Unable to sync to the database, Error: ${err.message}`); - throw err; - } - } - - /** - * 实始化数据(必须在DB初始化表结构后调用) - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @access None - * @param initArgs 初始化参数 - */ - async initDatas(initArgs: DataInitArgs): Promise { - const t = await this.sequelize.transaction(); - try { - if (initArgs.identityResources?.length) { - // Initialize identity resources - const identityResources = await IdentityResources.bulkCreate( - initArgs.identityResources.map( - ({ claims: _ignored0, properties: _ignored1, ...identityResource }) => identityResource, - ), - { transaction: t }, - ); - - const identityClaimsCreation = initArgs.identityResources.reduce((prev, item, index) => { - return prev.concat( - item.claims?.map((claim) => ({ - identityResourceId: identityResources[index].id, - type: claim, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - identityClaimsCreation.length && (await IdentityClaims.bulkCreate(identityClaimsCreation, { transaction: t })); - - const identityPropertiesCreation = initArgs.identityResources.reduce((prev, item, index) => { - return prev.concat( - item.properties?.map((property) => ({ - identityResourceId: identityResources[index].id, - ...property, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - identityPropertiesCreation.length && - (await IdentityProperties.bulkCreate(identityPropertiesCreation, { transaction: t })); - } - - // Initialize api resources - if (initArgs.apiResources?.length) { - const apiResources = await ApiResources.bulkCreate( - initArgs.apiResources.map( - ({ claims: _ignored0, scopes: _ignored1, secrets: _ignored2, properties: _ignored3, ...apiResource }) => - apiResource, - ), - { transaction: t }, - ); - - const apiClaimsCreation = initArgs.apiResources.reduce((prev, item, index) => { - return prev.concat( - item.claims?.map((claim) => ({ - apiResourceId: apiResources[index].id, - type: claim, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - apiClaimsCreation.length && (await ApiClaims.bulkCreate(apiClaimsCreation, { transaction: t })); - - const apiScopesCreation = initArgs.apiResources.reduce((prev, item, index) => { - return prev.concat( - item.scopes?.map(({ claims: _ignored0, ...scope }) => ({ - apiResourceId: apiResources[index].id, - ...scope, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - const apiScopes = apiScopesCreation.length - ? await ApiScopes.bulkCreate(apiScopesCreation, { transaction: t }) - : []; - - let scopeClaimsIndex = 0; - const apiScopeClaimsCreation = initArgs.apiResources.reduce((prev, item) => { - item.scopes?.forEach((scope) => { - prev = prev.concat( - scope.claims?.map((claim) => ({ - apiScopeId: apiScopes[scopeClaimsIndex].id, - type: claim, - })) ?? [], - ); - scopeClaimsIndex++; - }); - return prev; - }, [] as CreationAttributes[]); - - apiScopeClaimsCreation.length && (await ApiScopeClaims.bulkCreate(apiScopeClaimsCreation, { transaction: t })); - - const apiSecretsCreation = initArgs.apiResources.reduce((prev, item, index) => { - return prev.concat( - item.secrets?.map((secret) => ({ - apiResourceId: apiResources[index].id, - ...secret, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - apiSecretsCreation.length && (await ApiSecrets.bulkCreate(apiSecretsCreation, { transaction: t })); - - const apiPropertiesCreation = initArgs.apiResources.reduce((prev, item, index) => { - return prev.concat( - item.properties?.map((property) => ({ - apiResourceId: apiResources[index].id, - ...property, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - apiPropertiesCreation.length && (await ApiProperties.bulkCreate(apiPropertiesCreation, { transaction: t })); - } - - // Initialize clients - if (initArgs.clients?.length) { - const clients = await Clients.bulkCreate( - initArgs.clients.map( - ({ - claims: _ignored0, - corsOrigins: _ignored1, - scopes: _ignored2, - grantTypes: _ignored3, - redirectUris: _ignored4, - postLogoutRedirectUris: _ignored5, - secrets: _ignored6, - properties: _ignored7, - ...client - }) => client, - ), - { transaction: t }, - ); - - const clientClaimsCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.claims?.map((claim) => ({ - clientId: clients[index].id, - type: claim.type, - value: claim.value, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientClaimsCreation.length && (await ClientClaims.bulkCreate(clientClaimsCreation, { transaction: t })); - - const clientCorsOriginsCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.corsOrigins?.map((origin) => ({ - clientId: clients[index].id, - origin, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientCorsOriginsCreation.length && - (await ClientCorsOrigins.bulkCreate(clientCorsOriginsCreation, { transaction: t })); - - const clientScopesCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.scopes?.map((scope) => ({ - clientId: clients[index].id, - scope, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientScopesCreation.length && (await ClientScopes.bulkCreate(clientScopesCreation, { transaction: t })); - - const clientGrantTypesCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.grantTypes?.map((grantType) => ({ - clientId: clients[index].id, - grantType, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientGrantTypesCreation.length && - (await ClientGrantTypes.bulkCreate(clientGrantTypesCreation, { transaction: t })); - - const clientRedirectUrisCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.redirectUris?.map((redirectUri) => ({ - clientId: clients[index].id, - redirectUri, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientRedirectUrisCreation.length && - (await ClientRedirectUris.bulkCreate(clientRedirectUrisCreation, { transaction: t })); - - const clientPostLogoutRedirectUrisCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.postLogoutRedirectUris?.map((postLogoutRedirectUri) => ({ - clientId: clients[index].id, - postLogoutRedirectUri, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientPostLogoutRedirectUrisCreation.length && - (await ClientPostLogoutRedirectUris.bulkCreate(clientPostLogoutRedirectUrisCreation, { - transaction: t, - })); - - const clientSecretsCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.secrets?.map((secret) => ({ - clientId: clients[index].id, - ...secret, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientSecretsCreation.length && (await ClientSecrets.bulkCreate(clientSecretsCreation, { transaction: t })); - - const clientPropertiesCreation = initArgs.clients.reduce((prev, item, index) => { - return prev.concat( - item.properties?.map((property) => ({ - clientId: clients[index].id, - ...property, - })) ?? [], - ); - }, [] as CreationAttributes[]); - - clientPropertiesCreation.length && - (await ClientProperites.bulkCreate(clientPropertiesCreation, { transaction: t })); - } - - await t.commit(); - return true; - } catch (err) { - await t.rollback(); - throw err; - } - } -} - -export const dataSources = Object.values(DataSources); export * from './datasources'; +export * from './entities'; export * from './interfaces'; +export * from './sequelize'; +export * from './model/model'; +export * from './repository/repository'; diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/api-resource.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/api-resource.interface.ts index 6033319a..6423c093 100644 --- a/servers/identity-server/src/datasource/sequelize/interfaces/api-resource.interface.ts +++ b/servers/identity-server/src/datasource/sequelize/interfaces/api-resource.interface.ts @@ -1,10 +1,10 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import ApiResources from '../entities/api-resources.entity'; -import ApiClaims from '../entities/api-claims.entity'; -import ApiScopes from '../entities/api-scopes.entity'; -import ApiScopeClaims from '../entities/api-scope-claims.entity'; -import ApiSecrets from '../entities/api-secrets.entity'; -import ApiProperties from '../entities/api-properties.entity'; +import { ApiResources } from '../entities/api-resources.entity'; +import { ApiClaims } from '../entities/api-claims.entity'; +import { ApiScopes } from '../entities/api-scopes.entity'; +import { ApiScopeClaims } from '../entities/api-scope-claims.entity'; +import { ApiSecrets } from '../entities/api-secrets.entity'; +import { ApiProperties } from '../entities/api-properties.entity'; import { PagedArgs, Paged } from './paged.interface'; export interface ApiResourceModel extends Attributes { diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/client.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/client.interface.ts index 393dcc9f..32ca976d 100644 --- a/servers/identity-server/src/datasource/sequelize/interfaces/client.interface.ts +++ b/servers/identity-server/src/datasource/sequelize/interfaces/client.interface.ts @@ -1,13 +1,13 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import Clients from '../entities/clients.entity'; -import ClientCorsOrigins from '../entities/client-cors-origins.entity'; -import ClientClaims from '../entities/client-claims.entity'; -import ClientGrantTypes from '../entities/client-grant-types.entity'; -import ClientScopes from '../entities/client-scopes.entity'; -import ClientRedirectUris from '../entities/client-redirect-uris.entity'; -import ClientPostLogoutRedirectUris from '../entities/client-post-logout-redirect-uris.entity'; -import ClientSecrets from '../entities/client-secrets.entity'; -import ClientProperties from '../entities/client-properties.entity'; +import { Clients } from '../entities/clients.entity'; +import { ClientCorsOrigins } from '../entities/client-cors-origins.entity'; +import { ClientClaims } from '../entities/client-claims.entity'; +import { ClientGrantTypes } from '../entities/client-grant-types.entity'; +import { ClientScopes } from '../entities/client-scopes.entity'; +import { ClientRedirectUris } from '../entities/client-redirect-uris.entity'; +import { ClientPostLogoutRedirectUris } from '../entities/client-post-logout-redirect-uris.entity'; +import { ClientSecrets } from '../entities/client-secrets.entity'; +import { ClientProperties } from '../entities/client-properties.entity'; import { PagedArgs, Paged } from './paged.interface'; export interface ClientModel extends Attributes { diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/data-init-args.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/data-init-args.interface.ts deleted file mode 100644 index 8668b4e4..00000000 --- a/servers/identity-server/src/datasource/sequelize/interfaces/data-init-args.interface.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { CreationAttributes } from 'sequelize'; -import { default as ApiResources } from '../entities/api-resources.entity'; -import { default as ApiClaims } from '../entities/api-claims.entity'; -import { default as ApiScopes } from '../entities/api-scopes.entity'; -import { default as ApiScopeClaims } from '../entities/api-scope-claims.entity'; -import { default as ApiSecrets } from '../entities/api-secrets.entity'; -import { default as ApiProperties } from '../entities/api-properties.entity'; -import { default as IdentityResources } from '../entities/identity-resources.entity'; -import { default as IdentityClaims } from '../entities/identity-claims.entity'; -import { default as IdentityProperties } from '../entities/identity-properties.entity'; -import { default as Clients } from '../entities/clients.entity'; -import { default as ClientClaims } from '../entities/client-claims.entity'; -import { default as ClientCorsOrigins } from '../entities/client-cors-origins.entity'; -import { default as ClientScopes } from '../entities/client-scopes.entity'; -import { default as ClientGrantTypes } from '../entities/client-grant-types.entity'; -import { default as ClientRedirectUris } from '../entities/client-redirect-uris.entity'; -import { default as ClientPostLogoutRedirectUris } from '../entities/client-post-logout-redirect-uris.entity'; -import { default as ClientSecrets } from '../entities/client-secrets.entity'; -import { default as ClientProperties } from '../entities/client-properties.entity'; - -export interface DataInitArgs { - apiResources?: Array< - CreationAttributes & { - claims?: CreationAttributes['type'][]; - scopes?: Array< - Omit, 'apiResourceId'> & { - claims?: CreationAttributes['type'][]; - } - >; - secrets?: Omit, 'apiResourceId'>[]; - properties?: Omit, 'apiResourceId'>[]; - } - >; - identityResources?: Array< - CreationAttributes & { - claims?: CreationAttributes['type'][]; - properties?: Omit, 'identityResourceId'>[]; - } - >; - clients?: Array< - CreationAttributes & { - claims?: Omit, 'clientId'>[]; - corsOrigins?: CreationAttributes['origin'][]; - scopes?: CreationAttributes['scope'][]; - grantTypes?: CreationAttributes['grantType'][]; - redirectUris?: CreationAttributes['redirectUri'][]; - postLogoutRedirectUris?: CreationAttributes['postLogoutRedirectUri'][]; - secrets?: Omit, 'clientId'>[]; - properties?: Omit, 'clientId'>[]; - } - >; -} diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/identity-resource.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/identity-resource.interface.ts index 469daff4..2e31a152 100644 --- a/servers/identity-server/src/datasource/sequelize/interfaces/identity-resource.interface.ts +++ b/servers/identity-server/src/datasource/sequelize/interfaces/identity-resource.interface.ts @@ -1,7 +1,7 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import IdentityResources from '../entities/identity-resources.entity'; -import IdentityClaims from '../entities/identity-claims.entity'; -import IdentityProperties from '../entities/identity-properties.entity'; +import { IdentityResources } from '../entities/identity-resources.entity'; +import { IdentityClaims } from '../entities/identity-claims.entity'; +import { IdentityProperties } from '../entities/identity-properties.entity'; import { PagedArgs, Paged } from './paged.interface'; export interface IdentityResourceModel extends Attributes { diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/table-associate-func.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/table-associate-func.interface.ts deleted file mode 100644 index caa1722d..00000000 --- a/servers/identity-server/src/datasource/sequelize/interfaces/table-associate-func.interface.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ModelStatic } from 'sequelize'; -import ApiClaims from '../entities/api-claims.entity'; -import ApiProperties from '../entities/api-properties.entity'; -import ApiResources from '../entities/api-resources.entity'; -import ApiScopeClaims from '../entities/api-scope-claims.entity'; -import ApiScopes from '../entities/api-scopes.entity'; -import ApiSecrets from '../entities/api-secrets.entity'; -import ClientClaims from '../entities/client-claims.entity'; -import ClientCorsOrigins from '../entities/client-cors-origins.entity'; -import ClientGrantTypes from '../entities/client-grant-types.entity'; -import ClientPostLogoutRedirectUris from '../entities/client-post-logout-redirect-uris.entity'; -import ClientProperties from '../entities/client-properties.entity'; -import ClientRedirectUris from '../entities/client-redirect-uris.entity'; -import ClientScopes from '../entities/client-scopes.entity'; -import ClientSecrets from '../entities/client-secrets.entity'; -import Clients from '../entities/clients.entity'; -import IdentityClaims from '../entities/identity-claims.entity'; -import IdentityProperties from '../entities/identity-properties.entity'; -import IdentityResources from '../entities/identity-resources.entity'; - -export type Models = { - ApiClaims: ModelStatic; - ApiProperties: ModelStatic; - ApiResources: ModelStatic; - ApiScopeClaims: ModelStatic; - ApiScopes: ModelStatic; - ApiSecrets: ModelStatic; - ClientClaims: ModelStatic; - ClientCorsOrigins: ModelStatic; - ClientGrantTypes: ModelStatic; - ClientPostLogoutRedirectUris: ModelStatic; - ClientProperties: ModelStatic; - ClientRedirectUris: ModelStatic; - ClientScopes: ModelStatic; - ClientSecrets: ModelStatic; - Clients: ModelStatic; - IdentityClaims: ModelStatic; - IdentityProperties: ModelStatic; - IdentityResources: ModelStatic; -}; - -export interface TableAssociateFunc { - (models: Models): void; -} diff --git a/servers/identity-server/src/datasource/sequelize/interfaces/table-init-func.interface.ts b/servers/identity-server/src/datasource/sequelize/interfaces/table-init-func.interface.ts deleted file mode 100644 index 7fbd2d33..00000000 --- a/servers/identity-server/src/datasource/sequelize/interfaces/table-init-func.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Sequelize } from 'sequelize'; - -export type TableInitOptions = { - prefix: string; -}; - -export interface TableInitFunc { - (sequelize: Sequelize, options: TableInitOptions): void; -} diff --git a/servers/identity-server/src/datasource/sequelize/model/model.ts b/servers/identity-server/src/datasource/sequelize/model/model.ts new file mode 100644 index 00000000..a39fe4ad --- /dev/null +++ b/servers/identity-server/src/datasource/sequelize/model/model.ts @@ -0,0 +1,24 @@ +import { Sequelize, Model as OriginModel } from 'sequelize'; +import { Repository } from '../repository/repository'; + +export type ModelType = new ( + values?: TCreationAttributes, + options?: any, +) => Model; +export type ModelCtor = Repository; +export type ModelStatic = { new (): M }; + +export type InitOptions = { + prefix: string; +}; + +export abstract class Model< + TModelAttributes extends {} = any, + TCreationAttributes extends {} = TModelAttributes, +> extends OriginModel { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static initialize(sequelize: Sequelize, options: InitOptions): void { + throw new Error('Method initialize not implemented.'); + } + static associate?(): void; +} diff --git a/servers/identity-server/src/datasource/sequelize/repository/repository.ts b/servers/identity-server/src/datasource/sequelize/repository/repository.ts new file mode 100644 index 00000000..357a2d5e --- /dev/null +++ b/servers/identity-server/src/datasource/sequelize/repository/repository.ts @@ -0,0 +1,4 @@ +import { NonAbstract } from '../../shared/types'; +import { Model } from '..'; + +export type Repository = (new () => M) & NonAbstract; diff --git a/servers/identity-server/src/datasource/sequelize/sequelize.ts b/servers/identity-server/src/datasource/sequelize/sequelize.ts new file mode 100644 index 00000000..601d9097 --- /dev/null +++ b/servers/identity-server/src/datasource/sequelize/sequelize.ts @@ -0,0 +1,70 @@ +import { Sequelize as OriginalSequelize, Options as OriginalOptions } from 'sequelize'; +import { Model, ModelCtor } from './model/model'; +import { Repository } from './repository/repository'; + +export interface SequelizeOptions extends OriginalOptions { + models?: ModelCtor[]; + tablePrefix?: string; + repositoryMode?: boolean; +} + +export class Sequelize extends OriginalSequelize { + repositoryMode: boolean; + tablePrefix: string; + + constructor(database: string, username: string, password?: string, options?: SequelizeOptions); + constructor(database: string, username: string, options?: SequelizeOptions); + constructor(options?: SequelizeOptions); + constructor(uri: string, options?: SequelizeOptions); + constructor(...args: any[]) { + const lastArg = args[args.length - 1]; + const options = lastArg && typeof lastArg === 'object' ? (lastArg as SequelizeOptions) : undefined; + if (options) { + args[args.length - 1] = options; + } + super(...args); + + if (options) { + this.repositoryMode = !!options.repositoryMode; + this.tablePrefix = options.tablePrefix || ''; + + options.models && this.addModels(options.models); + } else { + this.repositoryMode = false; + this.tablePrefix = ''; + } + } + + model(model: string | ModelCtor) { + if (typeof model !== 'string') { + return super.model(model.name); + } + return super.model(model); + } + + addModels(models: ModelCtor[], tablePrefix?: string): void { + const definedModels = this.defineModels(models, tablePrefix); + this.associateModels(definedModels); + } + + getRepository(modelClass: new () => M): Repository { + return this.model(modelClass as any) as Repository; + } + + private defineModels(models: ModelCtor[], tablePrefix?: string): ModelCtor[] { + return models.map((model) => { + const definedModel = this.repositoryMode ? this.createRepositoryModel(model) : model; + definedModel.initialize(this, { prefix: tablePrefix || this.tablePrefix }); + + return definedModel; + }); + } + + private associateModels(models: ModelCtor[]): void { + models.map((model) => model.associate && model.associate()); + } + + private createRepositoryModel(modelClass: ModelCtor): ModelCtor { + return class extends modelClass {}; + } +} diff --git a/servers/identity-server/src/datasource/entities/types.ts b/servers/identity-server/src/datasource/shared/types.ts similarity index 73% rename from servers/identity-server/src/datasource/entities/types.ts rename to servers/identity-server/src/datasource/shared/types.ts index 3ca8d322..7c1035cd 100644 --- a/servers/identity-server/src/datasource/entities/types.ts +++ b/servers/identity-server/src/datasource/shared/types.ts @@ -1,3 +1,4 @@ +export type NonAbstract = { [P in keyof T]: T[P] }; /** * Type helper for making certain fields of an object optional. */ diff --git a/servers/identity-server/src/db.sync.ts b/servers/identity-server/src/db.sync.ts index 82d6f234..127952d1 100644 --- a/servers/identity-server/src/db.sync.ts +++ b/servers/identity-server/src/db.sync.ts @@ -1,42 +1,28 @@ import path from 'path'; import { UniqueConstraintError } from 'sequelize'; -import { Logger } from '@nestjs/common'; +import { Logger, INestApplication } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { FileEnv } from '@ace-pomelo/shared/server'; -import { DatabaseManager, name } from '@/datasource'; +import { IdentityDatasourceService, name } from '@/datasource'; import { version } from './version'; const logger = new Logger('DbSync', { timestamp: true }); // sync database -export async function syncDatabase(config: ConfigService) { - const connection = config.get('IDENTITY_DATABASE_CONNECTION') - ? config.get('IDENTITY_DATABASE_CONNECTION') - : { - database: config.getOrThrow('IDENTITY_DATABASE_NAME'), - username: config.getOrThrow('IDENTITY_DATABASE_USERNAME'), - password: config.getOrThrow('IDENTITY_DATABASE_PASSWORD'), - dialect: config.get('IDENTITY_DATABASE_DIALECT', 'mysql'), - host: config.get('IDENTITY_DATABASE_HOST', 'localhost'), - port: config.get('IDENTITY_DATABASE_PORT', 3306), - define: { - charset: config.get('IDENTITY_DATABASE_CHARSET', 'utf8'), - collate: config.get('IDENTITY_DATABASE_COLLATE', ''), - }, - }; - const tablePrefix = config.get('TABLE_PREFIX'); +export async function syncDatabase(app: INestApplication) { + const configService = app.get(ConfigService); + const datasourceService = app.get(IdentityDatasourceService); // db lock file - const lockfile = path.join(config.get('configPath')!, config.get('DBLOCK_FILE', 'db.lock')); + const lockfile = path.join( + configService.get('configPath')!, + configService.get('DBLOCK_FILE', 'db.lock'), + ); const fileEnv = FileEnv.getInstance(lockfile); // 初始化数据库 - const dbManager = - typeof connection === 'string' - ? new DatabaseManager(connection, { tablePrefix }) - : new DatabaseManager({ ...connection, tablePrefix }); - await dbManager - .sync({ + await datasourceService + .syncDB({ alter: false, // match: /_dev$/, // TODO: version compare @@ -54,10 +40,13 @@ export async function syncDatabase(config: ConfigService) { if (needInitDates) { logger.debug('Start to initialize datas!'); try { - const origin = config.get('server.origin', 'http://localhost:' + config.get('server.port', 3000)); - const webURL = config.get('WEB_URL', origin); + const origin = configService.get( + 'server.origin', + 'http://localhost:' + configService.get('server.port', 3000), + ); + const webURL = configService.get('WEB_URL', origin); - await dbManager.initDatas({ + await datasourceService.initDatas({ apiResources: [], identityResources: [ { diff --git a/servers/identity-server/src/oidc-config/oidc-config.service.ts b/servers/identity-server/src/oidc-config/oidc-config.service.ts index c8e188e6..9e0d4944 100644 --- a/servers/identity-server/src/oidc-config/oidc-config.service.ts +++ b/servers/identity-server/src/oidc-config/oidc-config.service.ts @@ -3,16 +3,16 @@ import wildcard from 'wildcard'; import psl from 'psl'; import { get, omitBy, isNil } from 'lodash'; import { - Provider, FindAccount, KoaContextWithOIDC, CookiesSetOptions, ClientMetadata, ClientAuthMethod, SigningAlgorithmWithNone, + Provider, errors, } from 'oidc-provider'; -import { OidcConfiguration, OidcModuleOptionsFactory } from 'nest-oidc-provider'; +import { OidcConfiguration, OidcModuleOptions, OidcModuleOptionsFactory } from 'nest-oidc-provider'; import { default as sanitizeHtml } from 'sanitize-html'; import { Injectable, Inject, Logger } from '@nestjs/common'; import { sha256, random, normalizeRoutePath } from '@ace-pomelo/shared/server'; @@ -45,13 +45,13 @@ export class OidcConfigService implements OidcModuleOptionsFactory { return normalizeRoutePath(new url.URL(this.options.issuer).pathname); } - async createModuleOptions() { + async createModuleOptions(): Promise { return { issuer: this.options.issuer, - path: normalizeRoutePath(this.options.path!), + path: this.options.path, oidc: await this.getConfiguration(), proxy: true, - factory: (issuer: string, config?: OidcConfiguration) => { + factory: (issuer, config) => { const provider = new Provider(issuer, config); // allow http,localhost in development mode if (this.options.debug) { @@ -87,7 +87,6 @@ export class OidcConfigService implements OidcModuleOptionsFactory { const wildcardUris = this.redirectUris?.filter(hasWildcardHost) || []; return wildcardUris.some(wildcardMatches.bind(undefined, redirectUri)); }; - // Skip redirecting invalid request error to client // https://github.com/panva/node-oidc-provider/blob/main/recipes/skip_redirect.md Object.defineProperty(errors.InvalidRequest.prototype, 'allow_redirect', { value: false }); @@ -105,13 +104,23 @@ export class OidcConfigService implements OidcModuleOptionsFactory { // checksession endpoint // @ts-expect-error no types const sessionName = provider.cookieName('session'); + const checkSessionRoute = '/connect/checksession'; + const discoveryRoute = '/.well-known/openid-configuration'; provider.use(async (ctx, next) => { - if (ctx.method === 'GET' && ctx.path === '/connect/checksession') { + // set issuer dynamically + // @ts-expect-error readonly types + provider.issuer = ctx.origin; + + if (ctx.method === 'GET' && ctx.path === checkSessionRoute) { ctx.type = 'html'; ctx.status = 200; ctx.body = renderCheckSessionTemplate({ globalPrefix: this.globalPrefix, sessionName }); } await next(); + + if (ctx.oidc.route === 'discovery') { + ctx.body.check_session_iframe = ctx.oidc.urlFor('discovery').replace(discoveryRoute, checkSessionRoute); + } }); // checksession session_state @@ -140,30 +149,44 @@ export class OidcConfigService implements OidcModuleOptionsFactory { } async getConfiguration(): Promise { - const scopes: OidcConfiguration['scopes'] = [], - claims: OidcConfiguration['claims'] = {}; + const claims: OidcConfiguration['claims'] = {}; // identity resources - await this.identityResourceDataSource.getList(['name', 'enabled', 'claims']).then((resources) => { - resources.forEach((resource) => { - scopes.push(resource.name); - claims[resource.name] = resource.claims?.map((claim) => claim.type) ?? []; - }, {} as NonNullable); - }); + try { + await this.identityResourceDataSource.getList(['name', 'enabled', 'claims']).then((resources) => { + resources.forEach((resource) => { + claims[resource.name] = resource.claims?.map((claim) => claim.type) ?? []; + }); + }); + } catch (e: any) { + this.logger.error(`Required to reload server after database initalized: ${e.message}`); + // TODO: DB sync after module initalized cause table not found + // Set as default claims as DB sync + Object.entries({ + openid: ['sub'], + profile: [ + 'role', + 'login_name', + 'display_name', + 'nice_name', + 'nick_name', + 'avatar', + 'gender', + 'locale', + 'timezone', + 'url', + 'updated_at', + ], + phone: ['phone_number', 'phone_number_verified'], + email: ['email', 'email_verified'], + }).forEach(([name, claim]) => { + claims[name] = claim; + }); + } // api resources // TODO: api resources - // required scopes - if (!scopes.includes('openid')) { - scopes.unshift('openid'); - } - - // no configured need scopes - if (!scopes.includes('offline_access')) { - scopes.push('offline_access'); - } - return { // static clients metadata clients: this.options.debug @@ -197,7 +220,6 @@ export class OidcConfigService implements OidcModuleOptionsFactory { }, ] : [], - scopes, claims, responseTypes: [ 'code', @@ -243,9 +265,6 @@ export class OidcConfigService implements OidcModuleOptionsFactory { return `${this.globalPrefix}/login?${params.toString()}`; }, }, - discovery: { - check_session_iframe: url.resolve(this.options.issuer, `${this.options.path}/connect/checksession`), - }, ttl: { DeviceCode: (ctx, token, client) => { return client.metadata()['device_code_ttl'] || 5 * 60; // 5 minutes in seconds diff --git a/servers/infrastructure-service/src/controllers/site-init.controller.ts b/servers/infrastructure-service/src/controllers/site-init.controller.ts index 620da20a..0d51151d 100644 --- a/servers/infrastructure-service/src/controllers/site-init.controller.ts +++ b/servers/infrastructure-service/src/controllers/site-init.controller.ts @@ -17,18 +17,19 @@ import { import { IgnoreDbCheckInterceptor } from '@/common/interceptors/db-check.interceptor'; import { getDefaultUserRoles } from '@/common/utils/user.util'; import { version } from '../version'; -import { name, InfrastructureDatasourceService } from '../datasource/index'; +import { name, InfrastructureDatasourceService, UserDataSource } from '../datasource'; import { SiteInitPayload } from './payload/site-init.payload'; @IgnoreDbCheckInterceptor() @Controller() export class SiteInitController { private logger = new Logger(SiteInitController.name, { timestamp: true }); - private readonly adminLoginName = 'admin'; + private readonly adminName = 'admin'; private readonly fileEnv: FileEnv; constructor( - private readonly infrastructureDatasourceService: InfrastructureDatasourceService, + private readonly datasourceService: InfrastructureDatasourceService, + private readonly userDataSource: UserDataSource, readonly config: ConfigService, ) { const lockfile = path.join(config.get('configPath')!, config.get('DBLOCK_FILE', 'db.lock')); @@ -36,9 +37,7 @@ export class SiteInitController { } private checkAdminExists() { - return this.infrastructureDatasourceService.models.Users.count({ where: { loginName: this.adminLoginName } }).then( - (count) => count > 0, - ); + return this.userDataSource.isLoginNameExists(this.adminName); } @MessagePattern(SiteInitPattern.IsRequired) @@ -64,10 +63,10 @@ export class SiteInitController { this.logger.debug('Start to initialize datas!'); try { const timezoneOffset = -new Date().getTimezoneOffset(); - await this.infrastructureDatasourceService.initDatas({ + await this.datasourceService.initDatas({ users: [ { - loginName: this.adminLoginName, + loginName: this.adminName, loginPwd: md5(payload.password), niceName: 'Admin', displayName: 'Admin', diff --git a/servers/infrastructure-service/src/controllers/user.constroller.ts b/servers/infrastructure-service/src/controllers/user.constroller.ts index bba64aeb..bedf13d4 100644 --- a/servers/infrastructure-service/src/controllers/user.constroller.ts +++ b/servers/infrastructure-service/src/controllers/user.constroller.ts @@ -1,7 +1,7 @@ import { Controller, ParseIntPipe, ParseArrayPipe } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; import { UserCapability, UserPattern, ValidatePayloadExistsPipe } from '@ace-pomelo/shared/server'; -import { UserDataSource, UserModel, PagedUserModel, UserMetaModel, NewUserMetaInput } from '../datasource/index'; +import { UserDataSource, UserModel, PagedUserModel, UserMetaModel, NewUserMetaInput } from '../datasource'; import { Nullable } from '../types'; import { createMetaController } from './meta.controller'; import { diff --git a/servers/infrastructure-service/src/datasource/constants.ts b/servers/infrastructure-service/src/datasource/constants.ts index ab498acc..21ab49a4 100644 --- a/servers/infrastructure-service/src/datasource/constants.ts +++ b/servers/infrastructure-service/src/datasource/constants.ts @@ -1 +1 @@ -export const INFRASTRUCTURE_OPTIONS = 'INFRASTRUCTURE_OPTIONS'; +export const INFRASTRUCTURE_DATASOURCE_OPTIONS = 'INFRASTRUCTURE_DATASOURCE_OPTIONS'; diff --git a/servers/infrastructure-service/src/datasource/datasource.module.ts b/servers/infrastructure-service/src/datasource/datasource.module.ts index 23dd1871..ddecee24 100644 --- a/servers/infrastructure-service/src/datasource/datasource.module.ts +++ b/servers/infrastructure-service/src/datasource/datasource.module.ts @@ -4,9 +4,11 @@ import { InfrastructureDatasourceAsyncOptions, InfrastructureDatasourceOptionsFactory, } from './interfaces/infrastructure-datasource-options.interface'; -import { dataSources } from './sequelize/index'; +import * as DataSources from './sequelize/datasources'; import { InfrastructureDatasourceService } from './datasource.service'; -import { INFRASTRUCTURE_OPTIONS } from './constants'; +import { INFRASTRUCTURE_DATASOURCE_OPTIONS } from './constants'; + +const dataSources = Object.values(DataSources); @Module({}) export class InfrastructureDatasourceModule { @@ -22,13 +24,13 @@ export class InfrastructureDatasourceModule { global: isGlobal, providers: [ { - provide: INFRASTRUCTURE_OPTIONS, + provide: INFRASTRUCTURE_DATASOURCE_OPTIONS, useValue: restOptions, }, ...dataSources, InfrastructureDatasourceService, ], - exports: [INFRASTRUCTURE_OPTIONS, ...dataSources, InfrastructureDatasourceService], + exports: [INFRASTRUCTURE_DATASOURCE_OPTIONS, ...dataSources, InfrastructureDatasourceService], }; } @@ -38,7 +40,7 @@ export class InfrastructureDatasourceModule { global: options.isGlobal, imports: options.imports, providers: [...this.createAsyncProviders(options), ...dataSources, InfrastructureDatasourceService], - exports: [INFRASTRUCTURE_OPTIONS, ...dataSources, InfrastructureDatasourceService], + exports: [INFRASTRUCTURE_DATASOURCE_OPTIONS, ...dataSources, InfrastructureDatasourceService], }; } @@ -58,7 +60,7 @@ export class InfrastructureDatasourceModule { private static createAsyncOptionsProvider(options: InfrastructureDatasourceAsyncOptions): Provider { if (options.useFactory) { return { - provide: INFRASTRUCTURE_OPTIONS, + provide: INFRASTRUCTURE_DATASOURCE_OPTIONS, useFactory: async (...args: any[]) => { const moduleOptions = await options.useFactory!(...args); // check connection config @@ -69,7 +71,7 @@ export class InfrastructureDatasourceModule { }; } return { - provide: INFRASTRUCTURE_OPTIONS, + provide: INFRASTRUCTURE_DATASOURCE_OPTIONS, useFactory: async (optionsFactory: InfrastructureDatasourceOptionsFactory) => { const moduleOptions = await optionsFactory.createSequlizeOptions(); // check connection config diff --git a/servers/infrastructure-service/src/datasource/datasource.service.ts b/servers/infrastructure-service/src/datasource/datasource.service.ts index 0be159be..6c66bb41 100644 --- a/servers/infrastructure-service/src/datasource/datasource.service.ts +++ b/servers/infrastructure-service/src/datasource/datasource.service.ts @@ -1,30 +1,227 @@ -import { Sequelize } from 'sequelize'; -import { Injectable, Inject, OnApplicationShutdown } from '@nestjs/common'; +import { Injectable, Inject, Logger, OnApplicationShutdown } from '@nestjs/common'; +import { SyncOptions, CreationAttributes } from 'sequelize'; import { InfrastructureDatasourceOptions } from './interfaces/infrastructure-datasource-options.interface'; -import { Models } from './sequelize/interfaces/table-associate-func.interface'; -import { DatabaseManager } from './sequelize/index'; -import { INFRASTRUCTURE_OPTIONS } from './constants'; +import { DataInitArgs } from './interfaces/data-init-args.interface'; +import { Sequelize, SequelizeOptions } from './sequelize/sequelize'; +import { + Comments, + CommentMeta, + Links, + Medias, + MediaMeta, + Options, + Templates, + TemplateMeta, + TermTaxonomy, + TermTaxonomyMeta, + TermRelationships, + Users, + UserMeta, +} from './sequelize/entities'; +import { INFRASTRUCTURE_DATASOURCE_OPTIONS } from './constants'; @Injectable() export class InfrastructureDatasourceService implements OnApplicationShutdown { - sequelize: Sequelize; - models: Models; - initDatas: DatabaseManager['initDatas']; + private readonly logger = new Logger(Sequelize.name, { timestamp: true }); + readonly sequelize: Readonly; - constructor(@Inject(INFRASTRUCTURE_OPTIONS) options: InfrastructureDatasourceOptions) { - const dbManager = + constructor(@Inject(INFRASTRUCTURE_DATASOURCE_OPTIONS) private readonly options: InfrastructureDatasourceOptions) { + const sequelizeOptions: SequelizeOptions = { + models: [ + Comments, + CommentMeta, + Links, + Medias, + MediaMeta, + Options, + Templates, + TemplateMeta, + TermTaxonomy, + TermTaxonomyMeta, + TermRelationships, + Users, + UserMeta, + ], + tablePrefix: options.tablePrefix, + define: { + freezeTableName: true, + underscored: true, + timestamps: true, + createdAt: true, + updatedAt: true, + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_520_ci', + }, + }; + this.sequelize = typeof options.connection === 'string' - ? new DatabaseManager(options.connection, { - tablePrefix: options.tablePrefix, - }) - : new DatabaseManager({ + ? new Sequelize(options.connection, sequelizeOptions) + : new Sequelize({ + ...sequelizeOptions, ...options.connection, - tablePrefix: options.tablePrefix, + define: { + ...sequelizeOptions.define, + ...options.connection.define, + }, }); + } + + get tablePrefix() { + return this.options.tablePrefix || ''; + } + + get translate() { + return this.options.translate || ((key, fallback) => fallback); + } + + /** + * 初始化数据库 + * @author Hubert + * @since 2022-05-01 + * @version 0.0.1 + * @param options 初始化参数 + * @returns true: 生成数据库成功;false: 跳过数据库生成(when 条件不满足) 否则抛出 Error + */ + async syncDB( + options?: SyncOptions & { when?: boolean | ((sequelize: Readonly) => Promise) }, + ): Promise { + try { + await this.sequelize.authenticate(); + } catch (err: any) { + this.logger.error(`Unable to connect to the database, Error: ${err.message}`); + throw err; + } + + try { + // eslint-disable-next-line prefer-const + let { when = true, ...syncOptions } = options || {}; + if (typeof when === 'function') { + when = await when.call(null, this.sequelize); + } + if (when) { + await this.sequelize.sync(syncOptions); + return true; + } + return false; + } catch (err: any) { + this.logger.error(`Unable to sync to the database, Error: ${err.message}`); + throw err; + } + } + + /** + * 实始化数据(必须在DB初始化表结构后调用) + * @author Hubert + * @since 2022-05-01 + * @version 0.0.1 + * @param initArgs 初始化参数 + */ + async initDatas(initArgs: DataInitArgs): Promise { + const t = await this.sequelize.transaction(); + try { + const optionsCreation: CreationAttributes[] = + initArgs.options?.map((option) => ({ + ...option, + optionName: option.nameWithTablePrefix ? `${this.tablePrefix}${option.optionName}` : option.optionName, + })) ?? []; + + // 创建用户 + let defaultUserId = 0; + if (initArgs.users?.length) { + const users = await Users.bulkCreate( + initArgs.users.map(({ metas: _ignored0, ...user }) => user), + { transaction: t }, + ); + + defaultUserId = users[0].id; + + const userMetasCreation = initArgs.users.reduce((prev, item, index) => { + return prev.concat( + item.metas?.map((meta) => ({ + ...meta, + metaKey: meta.keyWithTablePrefix ? `${this.tablePrefix}${meta.metaKey}` : meta.metaKey, + userId: users[index].id, + })) || [], + ); + }, [] as CreationAttributes[]); + + userMetasCreation.length && (await UserMeta.bulkCreate(userMetasCreation, { transaction: t })); + } + + // 创建分类 + if (initArgs.taxonomies?.length) { + const taxonomies = await TermTaxonomy.bulkCreate( + initArgs.taxonomies.map(({ metas: _ignored0, optionName: _ignored1, ...taxonomy }) => ({ + ...taxonomy, + slug: taxonomy.slug || taxonomy.name, + })), + { transaction: t }, + ); + + const termTaxonomyMetasCreation = initArgs.taxonomies.reduce((prev, item, index) => { + return prev.concat( + item.metas?.map((meta) => ({ + termTaxonomyId: taxonomies[index].id, + ...meta, + })) || [], + ); + }, [] as CreationAttributes[]); + + termTaxonomyMetasCreation.length && + (await TermTaxonomyMeta.bulkCreate(termTaxonomyMetasCreation, { transaction: t })); + + // 将 id 作为 optionValue 保存到 Options 表中 + initArgs.taxonomies.forEach((item, index) => { + if (item.optionName) { + optionsCreation.push({ + optionName: item.optionNameWithTablePrefix ? `${this.tablePrefix}${item.optionName}` : item.optionName, + optionValue: taxonomies[index].id.toString(), + }); + } + }); + } + + // 创建模板 + if (initArgs.templates?.length) { + const templates = await Templates.bulkCreate( + initArgs.templates.map(({ metas: _ignored0, optionName: _ignored1, ...template }) => ({ + ...template, + author: defaultUserId, + })), + { transaction: t }, + ); + + const templateMetasCreation = initArgs.templates.reduce((prev, item, index) => { + return prev.concat( + item.metas?.map((meta) => ({ + templateId: templates[index].id, + ...meta, + })) || [], + ); + }, [] as CreationAttributes[]); + + templateMetasCreation.length && (await TemplateMeta.bulkCreate(templateMetasCreation, { transaction: t })); + + // 将 id 作为 optionValue 保存到 Options 表中 + initArgs.templates.forEach((item, index) => { + if (item.optionName) { + optionsCreation.push({ + optionName: item.optionNameWithTablePrefix ? `${this.tablePrefix}${item.optionName}` : item.optionName, + optionValue: templates[index].id.toString(), + }); + } + }); + } + + // 创建选项 + optionsCreation.length && (await Options.bulkCreate(optionsCreation, { transaction: t })); - this.sequelize = dbManager.sequelize; - this.models = dbManager.associate(); - this.initDatas = dbManager.initDatas.bind(dbManager); + await t.commit(); + return true; + } catch (err) { + await t.rollback(); + throw err; + } } onApplicationShutdown() { diff --git a/servers/infrastructure-service/src/datasource/entities/comment-meta.entity.ts b/servers/infrastructure-service/src/datasource/entities/comment-meta.entity.ts index bb824ca2..9ac67382 100644 --- a/servers/infrastructure-service/src/datasource/entities/comment-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/comment-meta.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface CommentMetaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/comments.entity.ts b/servers/infrastructure-service/src/datasource/entities/comments.entity.ts index 3f950ec9..a7214c3b 100644 --- a/servers/infrastructure-service/src/datasource/entities/comments.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/comments.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface CommentAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/links.entity.ts b/servers/infrastructure-service/src/datasource/entities/links.entity.ts index c2d3bd6e..872e2787 100644 --- a/servers/infrastructure-service/src/datasource/entities/links.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/links.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface LinkAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/media-meta.entity.ts b/servers/infrastructure-service/src/datasource/entities/media-meta.entity.ts index 02f542bd..4c6218f8 100644 --- a/servers/infrastructure-service/src/datasource/entities/media-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/media-meta.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface MediaMetaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/medias.entity.ts b/servers/infrastructure-service/src/datasource/entities/medias.entity.ts index 9256ebe4..ccad4980 100644 --- a/servers/infrastructure-service/src/datasource/entities/medias.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/medias.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface MediaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/options.entity.ts b/servers/infrastructure-service/src/datasource/entities/options.entity.ts index c56cb380..e8a3a153 100644 --- a/servers/infrastructure-service/src/datasource/entities/options.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/options.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface OptionAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/template-meta.entity.ts b/servers/infrastructure-service/src/datasource/entities/template-meta.entity.ts index 232090aa..aeb56d61 100644 --- a/servers/infrastructure-service/src/datasource/entities/template-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/template-meta.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface TemplateMetaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/template.entity.ts b/servers/infrastructure-service/src/datasource/entities/template.entity.ts index 961dd3d1..6d5d8149 100644 --- a/servers/infrastructure-service/src/datasource/entities/template.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/template.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface TemplateAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/term-taxonomy-meta.entity.ts b/servers/infrastructure-service/src/datasource/entities/term-taxonomy-meta.entity.ts index b84b0e5c..2212a885 100644 --- a/servers/infrastructure-service/src/datasource/entities/term-taxonomy-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/term-taxonomy-meta.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface TermTaxonomyMetaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/term-taxonomy.entity.ts b/servers/infrastructure-service/src/datasource/entities/term-taxonomy.entity.ts index 8fb937ad..3043a830 100644 --- a/servers/infrastructure-service/src/datasource/entities/term-taxonomy.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/term-taxonomy.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface TermTaxonomyAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/user-meta.entity.ts b/servers/infrastructure-service/src/datasource/entities/user-meta.entity.ts index 705c985c..845d494b 100644 --- a/servers/infrastructure-service/src/datasource/entities/user-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/user-meta.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface UserMetaAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/entities/users.entity.ts b/servers/infrastructure-service/src/datasource/entities/users.entity.ts index 88ab418a..4ae8f748 100644 --- a/servers/infrastructure-service/src/datasource/entities/users.entity.ts +++ b/servers/infrastructure-service/src/datasource/entities/users.entity.ts @@ -1,4 +1,4 @@ -import { Optional } from './types'; +import { Optional } from '../shared/types'; export interface UserAttributes { id: number; diff --git a/servers/infrastructure-service/src/datasource/index.ts b/servers/infrastructure-service/src/datasource/index.ts index 19e56f87..8f407375 100644 --- a/servers/infrastructure-service/src/datasource/index.ts +++ b/servers/infrastructure-service/src/datasource/index.ts @@ -2,6 +2,6 @@ export const name = 'INFRASTRUCTURE_DATASOURCE'; export * from './datasource.module'; export * from './datasource.service'; -export * from './interfaces/index'; -export * from './sequelize/index'; -export * from './entities/types'; +export * from './interfaces'; +export * from './sequelize'; +export * from './entities'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/data-init-args.interface.ts b/servers/infrastructure-service/src/datasource/interfaces/data-init-args.interface.ts similarity index 65% rename from servers/infrastructure-service/src/datasource/sequelize/interfaces/data-init-args.interface.ts rename to servers/infrastructure-service/src/datasource/interfaces/data-init-args.interface.ts index 279a0f50..00522dbd 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/data-init-args.interface.ts +++ b/servers/infrastructure-service/src/datasource/interfaces/data-init-args.interface.ts @@ -1,11 +1,13 @@ import { CreationAttributes } from 'sequelize'; -import { default as Options } from '../entities/options.entity'; -import { default as TermTaxonomy } from '../entities/term-taxonomy.entity'; -import { default as TermTaxonomyMeta } from '../entities/term-taxonomy-meta.entity'; -import { default as Templates } from '../entities/templates.entity'; -import { default as TemplateMeta } from '../entities/template-meta.entity'; -import { default as Users } from '../entities/users.entity'; -import { default as UserMeta } from '../entities/user-meta.entity'; +import { + Options, + TermTaxonomy, + TermTaxonomyMeta, + Templates, + TemplateMeta, + Users, + UserMeta, +} from '../sequelize/entities'; export interface DataInitArgs { users?: Array< diff --git a/servers/infrastructure-service/src/datasource/interfaces/index.ts b/servers/infrastructure-service/src/datasource/interfaces/index.ts index 24cb8e29..79f27641 100644 --- a/servers/infrastructure-service/src/datasource/interfaces/index.ts +++ b/servers/infrastructure-service/src/datasource/interfaces/index.ts @@ -1 +1,2 @@ export * from './infrastructure-datasource-options.interface'; +export * from './data-init-args.interface'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/base.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/base.datasource.ts index 3ba207e5..3f28c015 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/base.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/base.datasource.ts @@ -1,7 +1,6 @@ import { words, capitalize } from 'lodash'; import { ModelDefined, ModelStatic, ProjectionAlias, Dialect } from 'sequelize'; -import { ModuleRef } from '@nestjs/core'; -import { Logger, OnModuleInit } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { ForbiddenError, jsonSafelyParse, @@ -11,8 +10,7 @@ import { UserCapability, } from '@ace-pomelo/shared/server'; import { InfrastructureDatasourceService } from '../../datasource.service'; -import { InfrastructureDatasourceOptions } from '../../interfaces/infrastructure-datasource-options.interface'; -import { INFRASTRUCTURE_OPTIONS } from '../../constants'; +import { Options, UserMeta } from '../entities'; // https://github.com/microsoft/TypeScript/issues/47663 import type {} from 'sequelize/types/utils'; @@ -20,93 +18,32 @@ import type {} from 'sequelize/types/utils'; const __AUTOLOAD_OPTIONS__ = new Map(); // Autoload options 缓存 const __OPTIONS__ = new Map(); // Not autoload options 缓存 -export abstract class BaseDataSource implements OnModuleInit { +export abstract class BaseDataSource { protected readonly logger!: Logger; - private infrastructureService?: InfrastructureDatasourceService; - private infrastuctureOptions?: InfrastructureDatasourceOptions; - constructor(private readonly moduleRef: ModuleRef) { + constructor(protected readonly datasourceService: InfrastructureDatasourceService) { this.logger = new Logger(this.constructor.name, { timestamp: true }); } - async onModuleInit() { - !this.infrastructureService && - (this.infrastructureService = this.moduleRef?.get(InfrastructureDatasourceService, { strict: false })); - !this.infrastuctureOptions && - (this.infrastuctureOptions = this.moduleRef?.get(INFRASTRUCTURE_OPTIONS, { - strict: false, - })); - } - - private ensureInfrastuctureService() { - if (!this.infrastructureService) { - this.logger.warn('Please inject IdentityService or ModuleRef in SubClass constructor'); - throw new Error('InfrastructureService not initialized'); - } - } - - private ensureInfrastuctureOptions() { - if (!this.infrastuctureOptions) { - this.logger.warn('Please inject IdentityOptions or ModuleRef in SubClass constructor'); - throw new Error('IdentityOptions not initialized'); - } - } - /** - * Get dialect from infrastuctureOptions.connection - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Get dialect */ - protected get getDatabaseDialect() { - this.ensureInfrastuctureOptions(); - - return typeof this.infrastuctureOptions!.connection === 'string' - ? (this.infrastuctureOptions!.connection.split(':')[0] as Dialect) - : this.infrastuctureOptions!.connection.dialect ?? 'mysql'; + protected get databaseDialect(): Dialect { + return this.datasourceService.sequelize.getDialect() as Dialect; } /** - * Shortcut for this.infrastuctureOptions.tablePrefix - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Get table prefix */ protected get tablePrefix() { - this.ensureInfrastuctureOptions(); - - return this.infrastuctureOptions!.tablePrefix || ''; + return this.datasourceService.tablePrefix; } /** - * Shortcut for this.infrastuctureOptions.translate - * Inject IdentityOptions in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit + * Translate function */ protected get translate() { - this.ensureInfrastuctureOptions(); - - return this.infrastuctureOptions!.translate || ((key: string, fallback: string) => fallback); - } - - /** - * Shortcut for this.infrastructureService.sequelize - * Inject IdentityService in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit - */ - protected get sequelize() { - this.ensureInfrastuctureService(); - - return this.infrastructureService!.sequelize; - } - - /** - * Shortcut for this.infrastructureService.models - * Inject IdentityService in SubClass constructor when use this property before onModuleInit - * or inject ModuleRef in SubClass constructor when use this property after onModuleInit - */ - protected get models() { - this.ensureInfrastuctureService(); - - return this.infrastructureService!.models; + return this.datasourceService.translate; } /** @@ -120,7 +57,7 @@ export abstract class BaseDataSource implements OnModuleInit { } else { return (async () => { // 赋默认值,initialize 会多次执行 - const options = await this.models.Options.findAll({ + const options = await Options.findAll({ attributes: ['optionName', 'optionValue'], where: { autoload: OptionAutoload.Yes, @@ -197,7 +134,7 @@ export abstract class BaseDataSource implements OnModuleInit { model: ModelDefined, modelName?: string, ) { - return this.sequelize.col(`${modelName || model.name}.${String(this.field(fieldName, model))}`); + return this.datasourceService.sequelize.col(`${modelName || model.name}.${String(this.field(fieldName, model))}`); } protected async getUserCapabilities(userId: number): Promise { @@ -210,7 +147,7 @@ export abstract class BaseDataSource implements OnModuleInit { } > >((await this.getOption(OptionPresetKeys.UserRoles))!); - const userCapabilities = await this.models.UserMeta.findOne({ + const userCapabilities = await UserMeta.findOne({ attributes: ['metaValue'], where: { userId, @@ -282,7 +219,7 @@ export abstract class BaseDataSource implements OnModuleInit { } // 如果缓存中没有,从数据库查询 if (value === void 0) { - const options = await this.models.Options.findAll({ + const options = await Options.findAll({ attributes: ['optionName', 'optionValue'], where: { optionName: [optionName, `${this.tablePrefix}${optionName}`], diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/comment.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/comment.datasource.ts index 2e30480d..531a619f 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/comment.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/comment.datasource.ts @@ -1,6 +1,7 @@ -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ValidationError, UserCapability } from '@ace-pomelo/shared/server'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Comments, CommentMeta } from '../entities'; import { CommentModel, CommentMetaModel, @@ -14,8 +15,8 @@ import { MetaDataSource } from './meta.datasource'; @Injectable() export class CommentDataSource extends MetaDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } /** @@ -29,8 +30,8 @@ export class CommentDataSource extends MetaDataSource comment?.toJSON()); } @@ -40,8 +41,8 @@ export class CommentDataSource extends MetaDataSource { - return this.models.Comments.findAndCountAll({ - attributes: this.filterFields(fields, this.models.Comments), + return Comments.findAndCountAll({ + attributes: this.filterFields(fields, Comments), where: { ...query, }, @@ -63,9 +64,9 @@ export class CommentDataSource extends MetaDataSource { const { metas, ...rest } = model; - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { - const comment = await this.models.Comments.create( + const comment = await Comments.create( { ...rest, userId: requestUserId, @@ -74,7 +75,7 @@ export class CommentDataSource extends MetaDataSource { return { ...meta, @@ -105,7 +106,7 @@ export class CommentDataSource extends MetaDataSource { - const comment = await this.models.Comments.findByPk(id, { + const comment = await Comments.findByPk(id, { attributes: ['userId'], }); if (comment) { @@ -114,7 +115,7 @@ export class CommentDataSource extends MetaDataSource { - const comment = await this.models.Comments.findByPk(id); + const comment = await Comments.findByPk(id); if (comment) { // 非本人创建的是否可以删除 if (comment.userId !== requestUserId) { await this.hasCapability(UserCapability.ModerateComments, requestUserId); } - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { await comment.destroy({ transaction: t }); - await this.models.CommentMeta.destroy({ + await CommentMeta.destroy({ where: { commentId: id, }, diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/link.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/link.datasource.ts index f57b76f8..2c67634d 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/link.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/link.datasource.ts @@ -1,26 +1,27 @@ import { Op } from 'sequelize'; -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ValidationError, UserCapability } from '@ace-pomelo/shared/server'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Links } from '../entities'; import { LinkModel, PagedLinkModel, PagedLinkArgs, NewLinkInput, UpdateLinkInput } from '../interfaces/link.interface'; import { BaseDataSource } from './base.datasource'; @Injectable() export class LinkDataSource extends BaseDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } get(id: number, fields: string[]): Promise { - return this.models.Links.findByPk(id, { - attributes: this.filterFields(fields, this.models.Links), + return Links.findByPk(id, { + attributes: this.filterFields(fields, Links), }).then((link) => link?.toJSON()); } getPaged({ offset, limit, ...query }: PagedLinkArgs, fields: string[]): Promise { const { keyword, ...restQuery } = query; - return this.models.Links.findAndCountAll({ - attributes: this.filterFields(fields, this.models.Links), + return Links.findAndCountAll({ + attributes: this.filterFields(fields, Links), where: { ...(keyword ? { @@ -46,7 +47,7 @@ export class LinkDataSource extends BaseDataSource { * @param requestUserId 请求用户 Id */ async create(model: NewLinkInput, requestUserId: number): Promise { - const link = await this.models.Links.create({ + const link = await Links.create({ ...model, userId: requestUserId, }); @@ -64,7 +65,7 @@ export class LinkDataSource extends BaseDataSource { * @param requestUserId 请求用户 Id */ async update(id: number, model: UpdateLinkInput, requestUserId: number): Promise { - const link = await this.models.Links.findByPk(id, { + const link = await Links.findByPk(id, { attributes: ['userId'], }); if (link) { @@ -73,7 +74,7 @@ export class LinkDataSource extends BaseDataSource { await this.hasCapability(UserCapability.ManageLinks, requestUserId); } - await this.models.Links.update(model, { + await Links.update(model, { where: { id }, }); } else { @@ -93,7 +94,7 @@ export class LinkDataSource extends BaseDataSource { * @param requestUserId 请求用户 Id */ async delete(id: number, requestUserId: number): Promise { - const link = await this.models.Links.findByPk(id); + const link = await Links.findByPk(id); if (link) { // 非本人创建的是否可删除 if (link.userId !== requestUserId) { diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/media.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/media.datasource.ts index 11a18a92..0b7b7cea 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/media.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/media.datasource.ts @@ -1,7 +1,8 @@ import { Op, WhereOptions } from 'sequelize'; -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ValidationError, MediaMetaPresetKeys, UserCapability } from '@ace-pomelo/shared/server'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Medias, MediaMeta } from '../entities'; import { MediaModel, MediaMetaDataModel, @@ -16,8 +17,8 @@ import { MetaDataSource } from './meta.datasource'; @Injectable() export class MediaDataSource extends MetaDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } /** @@ -31,10 +32,10 @@ export class MediaDataSource extends MetaDataSource { return ( - (await this.models.Medias.count({ + (await Medias.count({ where: { fileName, }, @@ -176,9 +177,9 @@ export class MediaDataSource extends MetaDataSource; + return model; } /** @@ -272,7 +273,7 @@ export abstract class MetaDataSource model.metaKey), ); - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { if (falseOrMetaKeys) { // 支持重复key update diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/option.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/option.datasource.ts index 066b3b6c..258250c0 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/option.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/option.datasource.ts @@ -1,13 +1,14 @@ -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ValidationError, UserCapability } from '@ace-pomelo/shared/server'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Options } from '../entities'; import { OptionModel, OptionArgs, NewOptionInput, UpdateOptionInput } from '../interfaces/option.interface'; import { BaseDataSource } from './base.datasource'; @Injectable() export class OptionDataSource extends BaseDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } /** @@ -17,8 +18,8 @@ export class OptionDataSource extends BaseDataSource { * @param fields 返回的字段 */ get(id: number, fields: string[]): Promise { - return this.models.Options.findByPk(id, { - attributes: this.filterFields(fields, this.models.Options), + return Options.findByPk(id, { + attributes: this.filterFields(fields, Options), }).then((option) => { if (option) { const { optionName, ...rest } = option.toJSON(); @@ -41,8 +42,8 @@ export class OptionDataSource extends BaseDataSource { * @param fields 返回的字段 */ getList(query: OptionArgs, fields: string[]): Promise { - return this.models.Options.findAll({ - attributes: this.filterFields(fields, this.models.Options), + return Options.findAll({ + attributes: this.filterFields(fields, Options), where: { ...query, }, @@ -82,7 +83,7 @@ export class OptionDataSource extends BaseDataSource { */ async isExists(optionName: string) { return ( - (await this.models.Options.count({ + (await Options.count({ where: { optionName: [optionName, `${this.tablePrefix}${optionName}`], }, @@ -110,7 +111,7 @@ export class OptionDataSource extends BaseDataSource { ); } - const option = await this.models.Options.create(model); + const option = await Options.create(model); super.resetOptions(); return option.toJSON(); } @@ -124,7 +125,7 @@ export class OptionDataSource extends BaseDataSource { async update(id: number, model: UpdateOptionInput, requestUserId: number): Promise { await this.hasCapability(UserCapability.ManageOptions, requestUserId); - await this.models.Options.update(model, { + await Options.update(model, { where: { id }, }); super.resetOptions(); @@ -145,7 +146,7 @@ export class OptionDataSource extends BaseDataSource { async delete(id: number, requestUserId: number): Promise { await this.hasCapability(UserCapability.ManageOptions, requestUserId); - await this.models.Options.destroy({ + await Options.destroy({ where: { id }, }); super.resetOptions(); diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/template.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/template.datasource.ts index e443f405..865828bf 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/template.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/template.datasource.ts @@ -1,4 +1,3 @@ -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { WhereOptions, Attributes, Includeable, Transaction, Op, Order } from 'sequelize'; import { @@ -11,7 +10,8 @@ import { TemplateStatus, TemplatePresetType, } from '@ace-pomelo/shared/server'; -import { default as Templates } from '../entities/templates.entity'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Templates, TemplateMeta, TermTaxonomy, TermRelationships } from '../entities'; import { TemplateInnerStatus, TemplateInnerType, @@ -58,8 +58,8 @@ enum TemplateMetaPresetKeys { @Injectable() export class TemplateDataSource extends MetaDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } /** @@ -70,7 +70,7 @@ export class TemplateDataSource extends MetaDataSource[] | number[], t?: Transaction) { const templates = templatesOrtemplateIds.some((templateOrId) => typeof templateOrId === 'number') - ? await this.models.Templates.findAll({ + ? await Templates.findAll({ attributes: ['id', 'status'], where: { id: templatesOrtemplateIds as number[], @@ -205,7 +205,7 @@ export class TemplateDataSource extends MetaDataSource [ @@ -309,8 +309,8 @@ export class TemplateDataSource extends MetaDataSource template?.toJSON()); } @@ -396,8 +396,8 @@ export class TemplateDataSource extends MetaDataSource template?.toJSON()); } @@ -441,22 +441,22 @@ export class TemplateDataSource extends MetaDataSource) { if (taxonomyId) { include.push({ - model: this.models.TermRelationships, + model: TermRelationships, as: 'TermRelationships', include: [ { - model: this.models.TermTaxonomy, + model: TermTaxonomy, as: 'TermTaxonomy', duplicating: false, }, @@ -489,24 +489,24 @@ export class TemplateDataSource extends MetaDataSource { - const templates = await this.models.Templates.findAll({ + const templates = await Templates.findAll({ attributes: ['name'], where: { type, @@ -566,7 +566,7 @@ export class TemplateDataSource extends MetaDataSource) { if (taxonomyId) { include.push({ - model: this.models.TermRelationships, + model: TermRelationships, as: 'TermRelationships', attributes: [], include: [ { - model: this.models.TermTaxonomy, + model: TermTaxonomy, as: 'TermTaxonomy', attributes: [], duplicating: false, @@ -973,24 +973,24 @@ export class TemplateDataSource extends MetaDataSource { return { ...meta, @@ -1217,7 +1217,7 @@ export class TemplateDataSource extends MetaDataSource { - await this.models.Templates.update( + await Templates.update( { commentCount: count, }, @@ -1535,7 +1535,7 @@ export class TemplateDataSource extends MetaDataSource { - const metaStatus = await this.models.TemplateMeta.findOne({ + const metaStatus = await TemplateMeta.findOne({ attributes: ['id', 'metaValue'], where: { templateId: id, @@ -1543,7 +1543,7 @@ export class TemplateDataSource extends MetaDataSource { - const templates = await this.models.Templates.findAll({ + const templates = await Templates.findAll({ where: { id: ids, }, @@ -1630,7 +1630,7 @@ export class TemplateDataSource extends MetaDataSource template.id), @@ -1638,11 +1638,11 @@ export class TemplateDataSource extends MetaDataSource - this.models.Templates.update( + Templates.update( { status: (metas.find((meta) => meta.templateId === template.id)?.metaValue as TemplateStatus) ?? @@ -1656,7 +1656,7 @@ export class TemplateDataSource extends MetaDataSource meta.id), }, @@ -1685,7 +1685,7 @@ export class TemplateDataSource extends MetaDataSource { - const template = await this.models.Templates.findByPk(id); + const template = await Templates.findByPk(id); if (template) { // 非 trash 状态下不可以删除 if (template.status !== TemplateStatus.Trash) { @@ -1700,10 +1700,10 @@ export class TemplateDataSource extends MetaDataSource { - const templates = await this.models.Templates.findAll({ + const templates = await Templates.findAll({ where: { id: ids, }, @@ -1773,11 +1773,11 @@ export class TemplateDataSource extends MetaDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } /** @@ -34,8 +34,8 @@ export class TermTaxonomyDataSource extends MetaDataSource { const format = (term: Model) => { @@ -146,11 +146,11 @@ export class TermTaxonomyDataSource extends MetaDataSource { const format = (term: Model) => { return term.toJSON(); @@ -191,11 +191,11 @@ export class TermTaxonomyDataSource extends MetaDataSource { - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); const { name, slug, group, taxonomy, description, parentId } = model; try { // 添加类别 - const termTaxonomy = await this.models.TermTaxonomy.create( + const termTaxonomy = await TermTaxonomy.create( { name, slug: slug || name, @@ -234,7 +234,7 @@ export class TermTaxonomyDataSource extends MetaDataSource { const isExists = - (await this.models.TermRelationships.count({ + (await TermRelationships.count({ where: { objectId: model.objectId, termTaxonomyId: model.termTaxonomyId, @@ -251,13 +251,13 @@ export class TermTaxonomyDataSource extends MetaDataSource(); } @@ -267,7 +267,7 @@ export class TermTaxonomyDataSource extends MetaDataSource { - await this.models.TermTaxonomy.update(model, { + await TermTaxonomy.update(model, { where: { id, }, @@ -280,9 +280,9 @@ export class TermTaxonomyDataSource extends MetaDataSource { - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { - const count = await this.models.TermRelationships.destroy({ + const count = await TermRelationships.destroy({ where: { objectId, termTaxonomyId, @@ -292,7 +292,7 @@ export class TermTaxonomyDataSource extends MetaDataSource 0) { // 数量 -1 - await this.models.TermTaxonomy.increment('count', { + await TermTaxonomy.increment('count', { where: { id: termTaxonomyId, }, @@ -313,16 +313,16 @@ export class TermTaxonomyDataSource extends MetaDataSource { - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { - await this.models.TermRelationships.destroy({ + await TermRelationships.destroy({ where: { termTaxonomyId: id, }, transaction: t, }); - const termTaxonomy = await this.models.TermTaxonomy.findOne({ + const termTaxonomy = await TermTaxonomy.findOne({ where: { id, }, @@ -334,7 +334,7 @@ export class TermTaxonomyDataSource extends MetaDataSource { - const t = await this.sequelize.transaction(); + const t = await this.datasourceService.sequelize.transaction(); try { - await this.models.TermRelationships.destroy({ + await TermRelationships.destroy({ where: { termTaxonomyId: ids, }, transaction: t, }); - const termTaxonomies = await this.models.TermTaxonomy.findAll({ + const termTaxonomies = await TermTaxonomy.findAll({ where: { id: ids, }, }); if (termTaxonomies.length) { - await this.models.TermTaxonomy.destroy({ + await TermTaxonomy.destroy({ where: { id: termTaxonomies.map(({ id }) => id), }, @@ -395,7 +395,7 @@ export class TermTaxonomyDataSource extends MetaDataSource - this.models.TermTaxonomy.update( + TermTaxonomy.update( { parentId: this.getParentId(termTaxonomies, termTaxonomy) }, { where: { diff --git a/servers/infrastructure-service/src/datasource/sequelize/datasources/user.datasource.ts b/servers/infrastructure-service/src/datasource/sequelize/datasources/user.datasource.ts index fac764a6..9828ab5d 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/datasources/user.datasource.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/datasources/user.datasource.ts @@ -2,7 +2,6 @@ import { isUndefined } from 'lodash'; import { CountryCode } from 'libphonenumber-js'; import { isEmail, isPhoneNumber } from 'class-validator'; import { WhereOptions, Attributes, Op } from 'sequelize'; -import { ModuleRef } from '@nestjs/core'; import { Injectable } from '@nestjs/common'; import { ForbiddenError, @@ -12,7 +11,8 @@ import { UserMetaPresetKeys, OptionPresetKeys, } from '@ace-pomelo/shared/server'; -import { default as User } from '../entities/users.entity'; +import { InfrastructureDatasourceService } from '../../datasource.service'; +import { Users, UserMeta } from '../entities'; import { UserModel, UserWithRoleModel, @@ -27,8 +27,8 @@ import { MetaDataSource } from './meta.datasource'; @Injectable() export class UserDataSource extends MetaDataSource { - constructor(moduleRef: ModuleRef) { - super(moduleRef); + constructor(datasourceService: InfrastructureDatasourceService) { + super(datasourceService); } private async getFieldsValue( @@ -40,11 +40,11 @@ export class UserDataSource extends MetaDataSource(OptionPresetKeys.DefaultPhoneNumberRegion) : undefined; if (id) { - return this.models.Users.findByPk(id, { + return Users.findByPk(id, { attributes: ['id', ...fields], }).then((user) => user?.toJSON() as any as UserModel); } else { - return this.models.Users.findOne({ + return Users.findOne({ attributes: ['id', ...fields], where: { [Op.or]: [ @@ -117,7 +117,7 @@ export class UserDataSource extends MetaDataSource { - return this.models.Users.findOne({ + return Users.findOne({ attributes: ['id'], where: { loginName: username, @@ -162,8 +162,8 @@ export class UserDataSource extends MetaDataSource field !== 'loginPwd'); - return this.models.Users.findAll({ - attributes: this.filterFields(fields, this.models.Users), + return Users.findAll({ + attributes: this.filterFields(fields, Users), where: { id: ids, }, @@ -197,7 +197,7 @@ export class UserDataSource extends MetaDataSource> = {}; + const where: WhereOptions> = {}; if (query.keyword) { // @ts-expect-error type error where[Op.or] = [ @@ -220,18 +220,17 @@ export class UserDataSource extends MetaDataSource field !== 'loginPwd'); - return this.models.Users.findAndCountAll({ - attributes: this.filterFields(fields, this.models.Users), + return Users.findAndCountAll({ + attributes: this.filterFields(fields, Users), include: [ { - model: this.models.UserMeta, + model: UserMeta, as: 'UserMetas', where: { metaKey: `${this.tablePrefix}${UserMetaPresetKeys.Capabilities}`, @@ -288,7 +287,7 @@ export class UserDataSource extends MetaDataSource { - return this.models.Users.count({ + return Users.count({ where: { loginName, }, @@ -350,7 +352,7 @@ export class UserDataSource extends MetaDataSource { - return this.models.Users.count({ + return Users.count({ where: { mobile, }, @@ -366,7 +368,7 @@ export class UserDataSource extends MetaDataSource { - return this.models.Users.count({ + return Users.count({ where: { email, }, @@ -414,9 +416,9 @@ export class UserDataSource extends MetaDataSource(OptionPresetKeys.DefaultPhoneNumberRegion) : undefined; const user = id - ? await this.models.Users.findOne({ + ? await Users.findOne({ where: { id, loginPwd: oldPwd, }, }) - : await this.models.Users.findOne({ + : await Users.findOne({ where: { [Op.or]: [ { loginName: username }, @@ -812,7 +814,7 @@ export class UserDataSource extends MetaDataSource { - const user = await this.models.Users.findByPk(id); + const user = await Users.findByPk(id); if (user) { if (user.status === UserStatus.Disabled) { throw new ValidationError( @@ -839,7 +841,7 @@ export class UserDataSource extends MetaDataSource { const region = await this.getOption(OptionPresetKeys.DefaultPhoneNumberRegion); - const user = await this.models.Users.findOne({ + const user = await Users.findOne({ where: { [Op.or]: [ { loginName: username }, @@ -877,13 +879,13 @@ export class UserDataSource extends MetaDataSource> { +export class CommentMeta extends Model> { public id!: number; public commentId!: number; public metaKey!: string; public metaValue?: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { +CommentMeta.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; CommentMeta.init( { diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/comments.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/comments.entity.ts index 3cd87870..301b4cad 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/comments.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/comments.entity.ts @@ -1,10 +1,10 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { CommentType } from '@ace-pomelo/shared/server'; import { CommentAttributes, CommentCreationAttributes } from '../../entities/comments.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { CommentMeta } from './comment-meta.entity'; -export default class Comments extends Model< +export class Comments extends Model< Omit, Optional< Omit, @@ -30,9 +30,9 @@ export default class Comments extends Model< public readonly updatedAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - Comments.init( +Comments.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -118,13 +118,13 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Comments.associate = function associate() { // Users.id <--> UserMeta.userId - models.Comments.hasMany(models.CommentMeta, { + Comments.hasMany(CommentMeta, { foreignKey: 'commentId', sourceKey: 'id', as: 'CommentMetas', constraints: false, }); - models.CommentMeta.belongsTo(models.Comments, { foreignKey: 'commentId', targetKey: 'id', constraints: false }); + CommentMeta.belongsTo(Comments, { foreignKey: 'commentId', targetKey: 'id', constraints: false }); }; diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/index.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/index.ts index b46cfff4..4fa3e7fd 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/index.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/index.ts @@ -1,13 +1,13 @@ -export * as CommentMeta from './comment-meta.entity'; -export * as Comments from './comments.entity'; -export * as Links from './links.entity'; -export * as MediaMeat from './media-meta.entity'; -export * as Medias from './medias.entity'; -export * as Options from './options.entity'; -export * as TemplateMeta from './template-meta.entity'; -export * as Templates from './templates.entity'; -export * as TermRelationships from './term-relationships.entity'; -export * as TermTaxonomyMeta from './term-taxonomy-meta.entity'; -export * as TermTaxonomy from './term-taxonomy.entity'; -export * as UserMeta from './user-meta.entity'; -export * as Users from './users.entity'; +export * from './comment-meta.entity'; +export * from './comments.entity'; +export * from './links.entity'; +export * from './media-meta.entity'; +export * from './medias.entity'; +export * from './options.entity'; +export * from './template-meta.entity'; +export * from './templates.entity'; +export * from './term-relationships.entity'; +export * from './term-taxonomy-meta.entity'; +export * from './term-taxonomy.entity'; +export * from './user-meta.entity'; +export * from './users.entity'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/links.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/links.entity.ts index f2db3805..492271c7 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/links.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/links.entity.ts @@ -1,10 +1,10 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { LinkVisible, LinkTarget } from '@ace-pomelo/shared/server'; import { LinkAttributes, LinkCreationAttributes } from '../../entities/links.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { TermRelationships } from './term-relationships.entity'; -export default class Links extends Model< +export class Links extends Model< Omit, Optional, 'visible' | 'userId'> > { @@ -20,9 +20,9 @@ export default class Links extends Model< public rss?: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - Links.init( +Links.initialize = function init(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -89,9 +89,9 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Links.associate = function associate() { // Links.id <--> TermRelationships.objectId - models.Links.hasMany(models.TermRelationships, { + Links.hasMany(TermRelationships, { foreignKey: 'objectId', sourceKey: 'id', as: 'LinkTermRelationships', diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/media-meta.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/media-meta.entity.ts index aeca3cb8..9cee3137 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/media-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/media-meta.entity.ts @@ -1,18 +1,18 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { MediaMetaPresetKeys } from '@ace-pomelo/shared/server'; import { MediaMetaAttributes, MediaMetaCreationAttributes } from '../../entities/media-meta.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class MediaMeta extends Model> { +export class MediaMeta extends Model> { public id!: number; public mediaId!: number; public metaKey!: string; public metaValue?: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { +MediaMeta.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; - MediaMeta.init( + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -44,7 +44,7 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }, ); - MediaMeta.addScope(MediaMetaPresetKeys.Matedata, { + this.addScope(MediaMetaPresetKeys.Matedata, { where: { metaKey: MediaMetaPresetKeys.Matedata, }, diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/medias.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/medias.entity.ts index 9e3d3ff8..6ca0c774 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/medias.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/medias.entity.ts @@ -1,10 +1,10 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { MediaMetaPresetKeys } from '@ace-pomelo/shared/server'; import { MediaAttributes, MediaCreationAttributes } from '../../entities/medias.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { MediaMeta } from './media-meta.entity'; -export default class Medias extends Model< +export class Medias extends Model< Omit, Optional, 'userId'> > { @@ -20,8 +20,8 @@ export default class Medias extends Model< public readonly createdAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); +Medias.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; Medias.init( { id: { @@ -76,17 +76,17 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Medias.associate = function associate() { // Medias.id <--> MediaMeta.mediaId - models.Medias.hasMany(models.MediaMeta, { + Medias.hasMany(MediaMeta, { foreignKey: 'mediaId', sourceKey: 'id', as: 'MediaMetas', constraints: false, }); - models.MediaMeta.belongsTo(models.Medias, { foreignKey: 'mediaId', targetKey: 'id', constraints: false }); + MediaMeta.belongsTo(Medias, { foreignKey: 'mediaId', targetKey: 'id', constraints: false }); - models.Medias.hasOne(models.MediaMeta.scope(MediaMetaPresetKeys.Matedata), { + Medias.hasOne(MediaMeta.scope(MediaMetaPresetKeys.Matedata), { foreignKey: 'mediaId', sourceKey: 'id', as: 'MediaMetadata', diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/options.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/options.entity.ts index edaccccd..4515ead1 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/options.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/options.entity.ts @@ -1,21 +1,18 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { OptionAutoload } from '@ace-pomelo/shared/server'; import { OptionAttributes, OptionCreationAttributes } from '../../entities/options.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class Options extends Model< - OptionAttributes, - Optional, 'autoload'> -> { +export class Options extends Model, 'autoload'>> { public id!: number; public optionName!: string; public optionValue!: string; public autoload!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - Options.init( +Options.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/template-meta.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/template-meta.entity.ts index b22af794..3f8c5236 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/template-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/template-meta.entity.ts @@ -1,20 +1,17 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { TemplateMetaAttributes, TemplateMetaMetaCreationAttributes } from '../../entities/template-meta.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class TemplateMeta extends Model< - TemplateMetaAttributes, - Omit -> { +export class TemplateMeta extends Model> { public id!: number; public templateId!: number; public metaKey!: string; public metaValue?: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { +TemplateMeta.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; - TemplateMeta.init( + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/templates.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/templates.entity.ts index 878f9adc..1eb20f90 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/templates.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/templates.entity.ts @@ -1,10 +1,11 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { TemplateStatus, TemplatePresetType, TemplateCommentStatus } from '@ace-pomelo/shared/server'; import { TemplateAttributes, TemplateCreationAttributes } from '../../entities/template.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { TemplateMeta } from './template-meta.entity'; +import { TermRelationships } from './term-relationships.entity'; -export default class Templates extends Model< +export class Templates extends Model< Omit, Optional< Omit, @@ -29,9 +30,9 @@ export default class Templates extends Model< public readonly updatedAt!: Date; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - Templates.init( +Templates.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -114,18 +115,18 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Templates.associate = function associate() { // Template.id <--> TemplateMeta.templateId - models.Templates.hasMany(models.TemplateMeta, { + Templates.hasMany(TemplateMeta, { foreignKey: 'templateId', sourceKey: 'id', as: 'TemplateMetas', constraints: false, }); - models.TemplateMeta.belongsTo(models.Templates, { foreignKey: 'templateId', targetKey: 'id', constraints: false }); + TemplateMeta.belongsTo(Templates, { foreignKey: 'templateId', targetKey: 'id', constraints: false }); // Template.id --> TermRelationships.objectId - models.Templates.hasMany(models.TermRelationships, { + Templates.hasMany(TermRelationships, { foreignKey: 'objectId', sourceKey: 'id', as: 'TemplateTermRelationships', diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/term-relationships.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/term-relationships.entity.ts index 93a75209..7efd422f 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/term-relationships.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/term-relationships.entity.ts @@ -1,11 +1,11 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { TermRelationshipAttributes, TermRelationshipCreationAttributes, } from '../../entities/term-relationships.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class TermRelationships extends Model< +export class TermRelationships extends Model< TermRelationshipAttributes, Optional > { @@ -14,9 +14,9 @@ export default class TermRelationships extends Model< public order!: number; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - TermRelationships.init( +TermRelationships.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { objectId: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy-meta.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy-meta.entity.ts index 3a6853ee..84c33370 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy-meta.entity.ts @@ -1,11 +1,11 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { TermTaxonomyMetaAttributes, TermTaxonomyMetaCreationAttributes, } from '../../entities/term-taxonomy-meta.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class TermTaxonomyMeta extends Model< +export class TermTaxonomyMeta extends Model< TermTaxonomyMetaAttributes, Omit > { @@ -15,9 +15,9 @@ export default class TermTaxonomyMeta extends Model< public metaValue!: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { +TermTaxonomyMeta.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; - TermTaxonomyMeta.init( + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy.entity.ts index afa9c90e..cfa7d65a 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/term-taxonomy.entity.ts @@ -1,9 +1,9 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { TermTaxonomyAttributes, TermTaxonomyCreationAttributes } from '../../entities/term-taxonomy.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { TermRelationships } from './term-relationships.entity'; -export default class TermTaxonomy extends Model< +export class TermTaxonomy extends Model< TermTaxonomyAttributes, Optional, 'parentId' | 'group' | 'count'> > { @@ -17,9 +17,9 @@ export default class TermTaxonomy extends Model< public count!: number; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { - const isMysql = sequelize.getDialect(); - TermTaxonomy.init( +TermTaxonomy.initialize = function initialize(sequelize, { prefix }) { + const isMysql = sequelize.getDialect() === 'mysql'; + this.init( { id: { type: isMysql ? DataTypes.BIGINT({ unsigned: true }) : DataTypes.BIGINT(), @@ -81,15 +81,15 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +TermTaxonomy.associate = function associate() { // TermTaxonomy.id <--> TermRelationships.termTaxonomyId - models.TermTaxonomy.hasMany(models.TermRelationships, { + TermTaxonomy.hasMany(TermRelationships, { foreignKey: 'termTaxonomyId', sourceKey: 'id', as: 'TermRelationships', constraints: false, }); - models.TermRelationships.belongsTo(models.TermTaxonomy, { + TermRelationships.belongsTo(TermTaxonomy, { foreignKey: 'termTaxonomyId', targetKey: 'id', constraints: false, diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/user-meta.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/user-meta.entity.ts index 1710289e..c2a2702d 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/user-meta.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/user-meta.entity.ts @@ -1,15 +1,15 @@ -import { Model, DataTypes } from 'sequelize'; +import { DataTypes } from 'sequelize'; import { UserMetaAttributes, UserMetaCreationAttributes } from '../../entities/user-meta.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; +import { Model } from '../model/model'; -export default class UserMeta extends Model> { +export class UserMeta extends Model> { public id!: number; public userId!: number; public metaKey!: string; public metaValue?: string; } -export const init: TableInitFunc = function init(sequelize, { prefix }) { +UserMeta.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; UserMeta.init( { diff --git a/servers/infrastructure-service/src/datasource/sequelize/entities/users.entity.ts b/servers/infrastructure-service/src/datasource/sequelize/entities/users.entity.ts index aabfe6d9..c4acbe41 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/entities/users.entity.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/entities/users.entity.ts @@ -1,10 +1,10 @@ -import { Model, Optional, DataTypes } from 'sequelize'; +import { Optional, DataTypes } from 'sequelize'; import { UserStatus } from '@ace-pomelo/shared/server'; import { UserAttributes, UserCreationAttributes } from '../../entities/users.entity'; -import { TableInitFunc } from '../interfaces/table-init-func.interface'; -import { TableAssociateFunc } from '../interfaces/table-associate-func.interface'; +import { Model } from '../model/model'; +import { UserMeta } from './user-meta.entity'; -export default class Users +export class Users extends Model< Omit, Optional, 'status'> @@ -29,7 +29,7 @@ export default class Users } // 初始化 -export const init: TableInitFunc = function init(sequelize, { prefix }) { +Users.initialize = function initialize(sequelize, { prefix }) { const isMysql = sequelize.getDialect() === 'mysql'; Users.init( { @@ -91,13 +91,13 @@ export const init: TableInitFunc = function init(sequelize, { prefix }) { }; // 关联 -export const associate: TableAssociateFunc = function associate(models) { +Users.associate = function associate() { // Users.id <--> UserMeta.userId - models.Users.hasMany(models.UserMeta, { + Users.hasMany(UserMeta, { foreignKey: 'userId', sourceKey: 'id', as: 'UserMetas', constraints: false, }); - models.UserMeta.belongsTo(models.Users, { foreignKey: 'userId', targetKey: 'id', constraints: false }); + UserMeta.belongsTo(Users, { foreignKey: 'userId', targetKey: 'id', constraints: false }); }; diff --git a/servers/infrastructure-service/src/datasource/sequelize/index.ts b/servers/infrastructure-service/src/datasource/sequelize/index.ts index 2dfdb2c0..04566732 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/index.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/index.ts @@ -1,247 +1,6 @@ -import { merge } from 'lodash'; -import { Logger } from '@nestjs/common'; -import { Sequelize, Options as SequelizeOptions, SyncOptions, ModelStatic, CreationAttributes } from 'sequelize'; -import * as DataSources from './datasources/index'; -import * as Entities from './entities/index'; -import { default as Options } from './entities/options.entity'; -import { default as TermTaxonomy } from './entities/term-taxonomy.entity'; -import { default as TermTaxonomyMeta } from './entities/term-taxonomy-meta.entity'; -import { default as Templates } from './entities/templates.entity'; -import { default as TemplateMeta } from './entities/template-meta.entity'; -import { default as Users } from './entities/users.entity'; -import { default as UserMeta } from './entities/user-meta.entity'; -import { TableInitFunc } from './interfaces/table-init-func.interface'; -import { Models, TableAssociateFunc } from './interfaces/table-associate-func.interface'; -import { DataInitArgs } from './interfaces/data-init-args.interface'; - -export interface DatabaseOptions extends SequelizeOptions { - tablePrefix?: string; -} - -export class DatabaseManager { - private logger = new Logger(DatabaseManager.name, { timestamp: true }); - private associated = false; - private readonly options: DatabaseOptions; - public readonly sequelize: Sequelize; - - constructor(options: DatabaseOptions); - constructor(uri: string, options?: DatabaseOptions); - constructor(uri: string | DatabaseOptions, options?: DatabaseOptions) { - options = typeof uri === 'string' ? options : uri; - this.options = merge(options || {}, { - define: { - freezeTableName: true, - underscored: true, - timestamps: true, - createdAt: true, - updatedAt: true, - charset: options?.define?.charset || 'utf8mb4', - collate: options?.define?.collate || 'utf8mb4_unicode_520_ci', - }, - }); - this.sequelize = typeof uri === 'string' ? new Sequelize(uri, this.options) : new Sequelize(this.options); - } - - /** - * 创建数据库表结构及关联关系 - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @returns 数据库模型 - */ - associate(): Models { - const models: Partial = {}; - const associates: TableAssociateFunc[] = []; - - for (const key in Entities) { - const { - init, - associate, - default: model, - } = ( - Entities as Record< - string, - { - init?: TableInitFunc; - associate?: TableAssociateFunc; - default: ModelStatic; - } - > - )[key]; - init?.(this.sequelize, { prefix: this.options.tablePrefix || '' }); - associate && associates.push(associate); - model && (models[model.name as keyof Models] = model); - } - - // associate needs to be called after all models are initialized - associates.forEach((associate) => associate(models as Models)); - - this.associated = true; - - return models as Models; - } - - /** - * 初始化数据库 - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @param options 初始化参数 - * @returns true: 生成数据库成功;false: 跳过数据库生成(when 条件不满足) 否则抛出 Error - */ - async sync( - options?: SyncOptions & { when?: boolean | ((sequelize: Sequelize) => Promise) }, - ): Promise { - try { - await this.sequelize.authenticate(); - } catch (err: any) { - this.logger.error(`Unable to connect to the database, Error: ${err.message}`); - throw err; - } - - // 初始化关联关系 - !this.associated && this.associate(); - - try { - // eslint-disable-next-line prefer-const - let { when = true, ...syncOptions } = options || {}; - if (typeof when === 'function') { - when = await when.call(null, this.sequelize); - } - if (when) { - await this.sequelize.sync(syncOptions); - return true; - } - return false; - } catch (err: any) { - this.logger.error(`Unable to sync to the database, Error: ${err.message}`); - throw err; - } - } - - /** - * 实始化数据(必须在DB初始化表结构后调用) - * @author Hubert - * @since 2022-05-01 - * @version 0.0.1 - * @access None - * @param initArgs 初始化参数 - */ - async initDatas(initArgs: DataInitArgs): Promise { - const t = await this.sequelize.transaction(); - try { - const optionsCreation: CreationAttributes[] = - initArgs.options?.map((option) => ({ - ...option, - optionName: option.nameWithTablePrefix - ? `${this.options.tablePrefix || ''}${option.optionName}` - : option.optionName, - })) ?? []; - - // 创建用户 - let defaultUserId = 0; - if (initArgs.users?.length) { - const users = await Users.bulkCreate( - initArgs.users.map(({ metas: _ignored0, ...user }) => user), - { transaction: t }, - ); - - defaultUserId = users[0].id; - - const userMetasCreation = initArgs.users.reduce((prev, item, index) => { - return prev.concat( - item.metas?.map((meta) => ({ - ...meta, - metaKey: meta.keyWithTablePrefix ? `${this.options.tablePrefix || ''}${meta.metaKey}` : meta.metaKey, - userId: users[index].id, - })) || [], - ); - }, [] as CreationAttributes[]); - - userMetasCreation.length && (await UserMeta.bulkCreate(userMetasCreation, { transaction: t })); - } - - // 创建分类 - if (initArgs.taxonomies?.length) { - const taxonomies = await TermTaxonomy.bulkCreate( - initArgs.taxonomies.map(({ metas: _ignored0, optionName: _ignored1, ...taxonomy }) => ({ - ...taxonomy, - slug: taxonomy.slug || taxonomy.name, - })), - { transaction: t }, - ); - - const termTaxonomyMetasCreation = initArgs.taxonomies.reduce((prev, item, index) => { - return prev.concat( - item.metas?.map((meta) => ({ - termTaxonomyId: taxonomies[index].id, - ...meta, - })) || [], - ); - }, [] as CreationAttributes[]); - - termTaxonomyMetasCreation.length && - (await TermTaxonomyMeta.bulkCreate(termTaxonomyMetasCreation, { transaction: t })); - - // 将 id 作为 optionValue 保存到 Options 表中 - initArgs.taxonomies.forEach((item, index) => { - if (item.optionName) { - optionsCreation.push({ - optionName: item.optionNameWithTablePrefix - ? `${this.options.tablePrefix || ''}${item.optionName}` - : item.optionName, - optionValue: taxonomies[index].id.toString(), - }); - } - }); - } - - // 创建模板 - if (initArgs.templates?.length) { - const templates = await Templates.bulkCreate( - initArgs.templates.map(({ metas: _ignored0, optionName: _ignored1, ...template }) => ({ - ...template, - author: defaultUserId, - })), - { transaction: t }, - ); - - const templateMetasCreation = initArgs.templates.reduce((prev, item, index) => { - return prev.concat( - item.metas?.map((meta) => ({ - templateId: templates[index].id, - ...meta, - })) || [], - ); - }, [] as CreationAttributes[]); - - templateMetasCreation.length && (await TemplateMeta.bulkCreate(templateMetasCreation, { transaction: t })); - - // 将 id 作为 optionValue 保存到 Options 表中 - initArgs.templates.forEach((item, index) => { - if (item.optionName) { - optionsCreation.push({ - optionName: item.optionNameWithTablePrefix - ? `${this.options.tablePrefix || ''}${item.optionName}` - : item.optionName, - optionValue: templates[index].id.toString(), - }); - } - }); - } - - // 创建选项 - optionsCreation.length && (await Options.bulkCreate(optionsCreation, { transaction: t })); - - await t.commit(); - return true; - } catch (err) { - await t.rollback(); - throw err; - } - } -} - -export const dataSources = Object.values(DataSources); -export * from './datasources/index'; -export * from './interfaces/index'; +export * from './datasources'; +export * from './entities'; +export * from './interfaces'; +export * from './sequelize'; +export * from './model/model'; +export * from './repository/repository'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/comment.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/comment.interface.ts index acdcd322..bceffd11 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/comment.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/comment.interface.ts @@ -1,5 +1,5 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import Comments from '../entities/comments.entity'; +import { Comments } from '../entities'; import { PagedArgs, Paged } from './paged.interface'; import { MetaModel, NewMetaInput } from './meta.interface'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/link.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/link.interface.ts index e7f176fd..0c1dd83b 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/link.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/link.interface.ts @@ -1,5 +1,5 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import Links from '../entities/links.entity'; +import { Links } from '../entities'; import { PagedArgs, Paged } from './paged.interface'; export interface LinkModel extends Attributes { diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/media.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/media.interface.ts index 77caebbe..9b3caff6 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/media.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/media.interface.ts @@ -1,5 +1,5 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import Medias from '../entities/medias.entity'; +import { Medias } from '../entities'; import { PagedArgs, Paged } from './paged.interface'; import { MetaModel, NewMetaInput } from './meta.interface'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/option.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/option.interface.ts index ecfff0ee..c215b1bb 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/option.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/option.interface.ts @@ -1,6 +1,6 @@ import { Attributes, CreationAttributes } from 'sequelize'; import { OptionAutoload } from '@ace-pomelo/shared/server'; -import Options from '../entities/options.entity'; +import { Options } from '../entities'; export interface OptionModel extends Attributes { readonly autoload: OptionAutoload; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-associate-func.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-associate-func.interface.ts deleted file mode 100644 index dbb9bfa5..00000000 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-associate-func.interface.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ModelStatic } from 'sequelize'; -import Comments from '../entities/comments.entity'; -import CommentMeta from '../entities/comment-meta.entity'; -import Medias from '../entities/medias.entity'; -import MediaMeta from '../entities/media-meta.entity'; -import Links from '../entities/links.entity'; -import TemplateMeta from '../entities/template-meta.entity'; -import Templates from '../entities/templates.entity'; -import Opitons from '../entities/options.entity'; -import TermTaxonomy from '../entities/term-taxonomy.entity'; -import TermTaxonomyMeta from '../entities/term-taxonomy-meta.entity'; -import TermRelationships from '../entities/term-relationships.entity'; -import Users from '../entities/users.entity'; -import UserMeta from '../entities/user-meta.entity'; - -export type Models = { - Options: ModelStatic; - Templates: ModelStatic; - TemplateMeta: ModelStatic; - Comments: ModelStatic; - CommentMeta: ModelStatic; - Medias: ModelStatic; - MediaMeta: ModelStatic; - Links: ModelStatic; - TermTaxonomy: ModelStatic; - TermTaxonomyMeta: ModelStatic; - TermRelationships: ModelStatic; - Users: ModelStatic; - UserMeta: ModelStatic; -}; - -export interface TableAssociateFunc { - (models: Models): void; -} diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-init-func.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-init-func.interface.ts deleted file mode 100644 index 7fbd2d33..00000000 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/table-init-func.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Sequelize } from 'sequelize'; - -export type TableInitOptions = { - prefix: string; -}; - -export interface TableInitFunc { - (sequelize: Sequelize, options: TableInitOptions): void; -} diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/template.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/template.interface.ts index 4932b50b..d13b296c 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/template.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/template.interface.ts @@ -1,6 +1,6 @@ import { Attributes, CreationAttributes } from 'sequelize'; import { TemplateStatus, TemplateCommentStatus } from '@ace-pomelo/shared/server'; -import Templates from '../entities/templates.entity'; +import { Templates } from '../entities'; import { PagedArgs, Paged } from './paged.interface'; import { MetaModel, NewMetaInput } from './meta.interface'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/term-taxonomy.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/term-taxonomy.interface.ts index 1e9da268..08b98a71 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/term-taxonomy.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/term-taxonomy.interface.ts @@ -1,6 +1,5 @@ import { Attributes, CreationAttributes } from 'sequelize'; -import TermTaxonomy from '../entities/term-taxonomy.entity'; -import TermRelationship from '../entities/term-relationships.entity'; +import { TermTaxonomy, TermRelationships } from '../entities'; import { MetaModel, NewMetaInput } from './meta.interface'; /** @@ -19,7 +18,7 @@ export interface NewTermTaxonomyMetaInput extends NewMetaInput { /** * 协议关系 */ -export interface TermRelationshipModel extends Attributes {} +export interface TermRelationshipModel extends Attributes {} /** * 协议(类别)搜索条件 @@ -67,4 +66,4 @@ export interface UpdateTermTaxonomyInput /** * 新建协议关系实体 */ -export interface NewTermRelationshipInput extends CreationAttributes {} +export interface NewTermRelationshipInput extends CreationAttributes {} diff --git a/servers/infrastructure-service/src/datasource/sequelize/interfaces/user.interface.ts b/servers/infrastructure-service/src/datasource/sequelize/interfaces/user.interface.ts index d084e3c9..7ac6fab3 100644 --- a/servers/infrastructure-service/src/datasource/sequelize/interfaces/user.interface.ts +++ b/servers/infrastructure-service/src/datasource/sequelize/interfaces/user.interface.ts @@ -1,6 +1,6 @@ import { Attributes, CreationAttributes } from 'sequelize'; import { UserStatus } from '@ace-pomelo/shared/server'; -import Users from '../entities/users.entity'; +import { Users } from '../entities'; import { PagedArgs, Paged } from './paged.interface'; import { MetaModel, NewMetaInput } from './meta.interface'; diff --git a/servers/infrastructure-service/src/datasource/sequelize/model/model.ts b/servers/infrastructure-service/src/datasource/sequelize/model/model.ts new file mode 100644 index 00000000..bf959cec --- /dev/null +++ b/servers/infrastructure-service/src/datasource/sequelize/model/model.ts @@ -0,0 +1,24 @@ +import { Sequelize, Model as OriginModel } from 'sequelize'; +import { Repository } from '../repository/repository'; + +export type ModelType = new ( + values?: TCreationAttributes, + options?: any, +) => Model; +export type ModelCtor = Repository; +export type ModelStatic = { new (): M }; + +export type InitOptions = { + prefix: string; +}; + +export abstract class Model< + TModelAttributes extends {} = any, + TCreationAttributes extends {} = TModelAttributes, +> extends OriginModel { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static initialize(sequelize: Sequelize, options: InitOptions): void { + throw new Error('Method not implemented.'); + } + static associate?(): void; +} diff --git a/servers/infrastructure-service/src/datasource/sequelize/repository/repository.ts b/servers/infrastructure-service/src/datasource/sequelize/repository/repository.ts new file mode 100644 index 00000000..357a2d5e --- /dev/null +++ b/servers/infrastructure-service/src/datasource/sequelize/repository/repository.ts @@ -0,0 +1,4 @@ +import { NonAbstract } from '../../shared/types'; +import { Model } from '..'; + +export type Repository = (new () => M) & NonAbstract; diff --git a/servers/infrastructure-service/src/datasource/sequelize/sequelize.ts b/servers/infrastructure-service/src/datasource/sequelize/sequelize.ts new file mode 100644 index 00000000..601d9097 --- /dev/null +++ b/servers/infrastructure-service/src/datasource/sequelize/sequelize.ts @@ -0,0 +1,70 @@ +import { Sequelize as OriginalSequelize, Options as OriginalOptions } from 'sequelize'; +import { Model, ModelCtor } from './model/model'; +import { Repository } from './repository/repository'; + +export interface SequelizeOptions extends OriginalOptions { + models?: ModelCtor[]; + tablePrefix?: string; + repositoryMode?: boolean; +} + +export class Sequelize extends OriginalSequelize { + repositoryMode: boolean; + tablePrefix: string; + + constructor(database: string, username: string, password?: string, options?: SequelizeOptions); + constructor(database: string, username: string, options?: SequelizeOptions); + constructor(options?: SequelizeOptions); + constructor(uri: string, options?: SequelizeOptions); + constructor(...args: any[]) { + const lastArg = args[args.length - 1]; + const options = lastArg && typeof lastArg === 'object' ? (lastArg as SequelizeOptions) : undefined; + if (options) { + args[args.length - 1] = options; + } + super(...args); + + if (options) { + this.repositoryMode = !!options.repositoryMode; + this.tablePrefix = options.tablePrefix || ''; + + options.models && this.addModels(options.models); + } else { + this.repositoryMode = false; + this.tablePrefix = ''; + } + } + + model(model: string | ModelCtor) { + if (typeof model !== 'string') { + return super.model(model.name); + } + return super.model(model); + } + + addModels(models: ModelCtor[], tablePrefix?: string): void { + const definedModels = this.defineModels(models, tablePrefix); + this.associateModels(definedModels); + } + + getRepository(modelClass: new () => M): Repository { + return this.model(modelClass as any) as Repository; + } + + private defineModels(models: ModelCtor[], tablePrefix?: string): ModelCtor[] { + return models.map((model) => { + const definedModel = this.repositoryMode ? this.createRepositoryModel(model) : model; + definedModel.initialize(this, { prefix: tablePrefix || this.tablePrefix }); + + return definedModel; + }); + } + + private associateModels(models: ModelCtor[]): void { + models.map((model) => model.associate && model.associate()); + } + + private createRepositoryModel(modelClass: ModelCtor): ModelCtor { + return class extends modelClass {}; + } +} diff --git a/servers/infrastructure-service/src/datasource/entities/types.ts b/servers/infrastructure-service/src/datasource/shared/types.ts similarity index 73% rename from servers/infrastructure-service/src/datasource/entities/types.ts rename to servers/infrastructure-service/src/datasource/shared/types.ts index 3ca8d322..7c1035cd 100644 --- a/servers/infrastructure-service/src/datasource/entities/types.ts +++ b/servers/infrastructure-service/src/datasource/shared/types.ts @@ -1,3 +1,4 @@ +export type NonAbstract = { [P in keyof T]: T[P] }; /** * Type helper for making certain fields of an object optional. */ diff --git a/servers/infrastructure-service/src/db.sync.ts b/servers/infrastructure-service/src/db.sync.ts index d392b51d..c8ad4f88 100644 --- a/servers/infrastructure-service/src/db.sync.ts +++ b/servers/infrastructure-service/src/db.sync.ts @@ -1,40 +1,26 @@ import path from 'path'; -import { Logger } from '@nestjs/common'; +import { Logger, INestApplication } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { FileEnv } from '@ace-pomelo/shared/server'; -import { DatabaseManager, name } from '@/datasource'; +import { InfrastructureDatasourceService, name } from '@/datasource'; const logger = new Logger('DbSync', { timestamp: true }); // sync database -export async function syncDatabase(config: ConfigService) { - const connection = config.get('INFRASTRUCTURE_DATABASE_CONNECTION') - ? config.get('INFRASTRUCTURE_DATABASE_CONNECTION') - : { - database: config.getOrThrow('INFRASTRUCTURE_DATABASE_NAME'), - username: config.getOrThrow('INFRASTRUCTURE_DATABASE_USERNAME'), - password: config.getOrThrow('INFRASTRUCTURE_DATABASE_PASSWORD'), - dialect: config.get('INFRASTRUCTURE_DATABASE_DIALECT', 'mysql'), - host: config.get('INFRASTRUCTURE_DATABASE_HOST', 'localhost'), - port: config.get('INFRASTRUCTURE_DATABASE_PORT', 3306), - define: { - charset: config.get('INFRASTRUCTURE_DATABASE_CHARSET', 'utf8'), - collate: config.get('INFRASTRUCTURE_DATABASE_COLLATE', ''), - }, - }; - const tablePrefix = config.get('TABLE_PREFIX'); +export async function syncDatabase(app: INestApplication) { + const configService = app.get(ConfigService); + const datasourceService = app.get(InfrastructureDatasourceService); // db lock - const lockfile = path.join(config.get('configPath')!, config.get('DBLOCK_FILE', 'db.lock')); + const lockfile = path.join( + configService.get('configPath')!, + configService.get('DBLOCK_FILE', 'db.lock'), + ); const fileEnv = FileEnv.getInstance(lockfile); // 初始化数据库 - const dbManager = - typeof connection === 'string' - ? new DatabaseManager(connection, { tablePrefix }) - : new DatabaseManager({ ...connection, tablePrefix }); - await dbManager - .sync({ + await datasourceService + .syncDB({ alter: false, // match: /_dev$/, // TODO: version compare @@ -45,6 +31,5 @@ export async function syncDatabase(config: ConfigService) { fileEnv.setEnv(name, 'PENDING'); logger.debug('Initialize database successful!'); } - 3; }); } diff --git a/servers/infrastructure-service/src/main.ts b/servers/infrastructure-service/src/main.ts index 067e43f7..d2ffc089 100644 --- a/servers/infrastructure-service/src/main.ts +++ b/servers/infrastructure-service/src/main.ts @@ -21,7 +21,7 @@ bootstrap(AppModule, { app.useLogger(app.get(Log4jsService)); // sync database - await syncDatabase(configService); + await syncDatabase(app); const host = configService.get('server.host', ''); const port = configService.get('server.port', 3000); diff --git a/yarn.lock b/yarn.lock index 36d2216c..c068dc0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11477,9 +11477,9 @@ __metadata: languageName: node linkType: hard -"dottie@npm:^2.0.4": +"dottie@npm:^2.0.6": version: 2.0.6 - resolution: "dottie@npm:2.0.6" + resolution: "dottie@npm:2.0.6::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fdottie%2F-%2Fdottie-2.0.6.tgz" checksum: 4c778df9dc631a1108a32ef390916836814999a7411d10883f4151bd49c9c6934dc329b3f50fc7692849aa75ba87dba880fd54be501a3b39a6b9c23d6f772a09 languageName: node linkType: hard @@ -11561,13 +11561,13 @@ __metadata: linkType: hard "ejs@npm:^3.1.8": - version: 3.1.9 - resolution: "ejs@npm:3.1.9::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fejs%2F-%2Fejs-3.1.9.tgz" + version: 3.1.10 + resolution: "ejs@npm:3.1.10::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fejs%2F-%2Fejs-3.1.10.tgz" dependencies: jake: ^10.8.5 bin: ejs: bin/cli.js - checksum: af6f10eb815885ff8a8cfacc42c6b6cf87daf97a4884f87a30e0c3271fedd85d76a3a297d9c33a70e735b97ee632887f85e32854b9cdd3a2d97edf931519a35f + checksum: ce90637e9c7538663ae023b8a7a380b2ef7cc4096de70be85abf5a3b9641912dde65353211d05e24d56b1f242d71185c6d00e02cb8860701d571786d92c71f05 languageName: node linkType: hard @@ -16259,20 +16259,20 @@ __metadata: languageName: node linkType: hard -"jose@npm:^4.10.3, jose@npm:^4.15.5": - version: 4.15.5 - resolution: "jose@npm:4.15.5::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fjose%2F-%2Fjose-4.15.5.tgz" - checksum: 7dde76447c7707bd4b448f914b216f3858e701aa83f00447434252461af5b9e159dcbffb88badea3f9616739526763581267c9560622f0a058df8d68c86d7f79 - languageName: node - linkType: hard - -"jose@npm:^4.15.9": +"jose@npm:^4.10.3, jose@npm:^4.15.9": version: 4.15.9 resolution: "jose@npm:4.15.9::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fjose%2F-%2Fjose-4.15.9.tgz" checksum: 41abe1c99baa3cf8a78ebbf93da8f8e50e417b7a26754c4afa21865d87527b8ac2baf66de2c5f6accc3f7d7158658dae7364043677236ea1d07895b040097f15 languageName: node linkType: hard +"jose@npm:^4.15.5": + version: 4.15.5 + resolution: "jose@npm:4.15.5::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fjose%2F-%2Fjose-4.15.5.tgz" + checksum: 7dde76447c7707bd4b448f914b216f3858e701aa83f00447434252461af5b9e159dcbffb88badea3f9616739526763581267c9560622f0a058df8d68c86d7f79 + languageName: node + linkType: hard + "jpeg-js@npm:^0.4.4": version: 0.4.4 resolution: "jpeg-js@npm:0.4.4" @@ -16727,8 +16727,8 @@ __metadata: linkType: hard "koa@npm:^2.13.4": - version: 2.15.0 - resolution: "koa@npm:2.15.0::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fkoa%2F-%2Fkoa-2.15.0.tgz" + version: 2.15.3 + resolution: "koa@npm:2.15.3::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fkoa%2F-%2Fkoa-2.15.3.tgz" dependencies: accepts: ^1.3.5 cache-content-type: ^1.0.0 @@ -16753,7 +16753,7 @@ __metadata: statuses: ^1.5.0 type-is: ^1.6.16 vary: ^1.1.2 - checksum: a97741f89f328f25ae94d82d0ee608377d89e086c73f2d868023e6050dea682ef93e0a5c80097f3aaad28121853aea50a7fb3c0c12ecc45798da2fd1255f580b + checksum: 7c3537443b1a588cf5c3e5554b914ff2bad510323d22b41861d5e0c97d47e9c5997965f303ede8be8bd83d309a4eea1f82cd45d35d6838bc21bb1bb6a90d5d25 languageName: node linkType: hard @@ -18404,7 +18404,7 @@ __metadata: "nest-oidc-provider@npm:^1.1.1": version: 1.1.1 - resolution: "nest-oidc-provider@npm:1.1.1" + resolution: "nest-oidc-provider@npm:1.1.1::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fnest-oidc-provider%2F-%2Fnest-oidc-provider-1.1.1.tgz" peerDependencies: "@nestjs/common": ">=8.0.0" "@nestjs/core": ">=8.0.0" @@ -19563,10 +19563,10 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.6.0": - version: 2.6.2 - resolution: "pg-connection-string@npm:2.6.2" - checksum: 22265882c3b6f2320785378d0760b051294a684989163d5a1cde4009e64e84448d7bf67d9a7b9e7f69440c3ee9e2212f9aa10dd17ad6773f6143c6020cebbcb5 +"pg-connection-string@npm:^2.6.1": + version: 2.7.0 + resolution: "pg-connection-string@npm:2.7.0::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fpg-connection-string%2F-%2Fpg-connection-string-2.7.0.tgz" + checksum: 68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd languageName: node linkType: hard @@ -19874,7 +19874,7 @@ __metadata: rxjs: ^7.2.0 sanitize-html: ^2.11.0 semver: ^7.5.1 - sequelize: ^6.19.0 + sequelize: ^6.30.0 source-map-support: ^0.5.20 statuses: ^2.0.1 stylelint: ^15.10.3 @@ -21987,21 +21987,21 @@ __metadata: languageName: node linkType: hard -"sequelize@npm:^6.19.0": - version: 6.32.1 - resolution: "sequelize@npm:6.32.1" +"sequelize@npm:^6.30.0": + version: 6.37.4 + resolution: "sequelize@npm:6.37.4::__archiveUrl=https%3A%2F%2Fregistry.npmmirror.com%2Fsequelize%2F-%2Fsequelize-6.37.4.tgz" dependencies: "@types/debug": ^4.1.8 "@types/validator": ^13.7.17 debug: ^4.3.4 - dottie: ^2.0.4 + dottie: ^2.0.6 inflection: ^1.13.4 lodash: ^4.17.21 moment: ^2.29.4 moment-timezone: ^0.5.43 - pg-connection-string: ^2.6.0 + pg-connection-string: ^2.6.1 retry-as-promised: ^7.0.4 - semver: ^7.5.1 + semver: ^7.5.4 sequelize-pool: ^7.1.0 toposort-class: ^1.0.1 uuid: ^8.3.2 @@ -22026,7 +22026,7 @@ __metadata: optional: true tedious: optional: true - checksum: 5284fd7a8eb8dfa8340c50c119b31006dda0931714d3e5a72bbfef3a07838018f935cc1b789a46486298043f10d553dbf9cf9cf60c46c6646ecfc9e9623a8b4c + checksum: 29adebae6e7dcb626a22281c87cd82e5ab270d357ac771a271287c9a481887f68dbce9b5edb8dfab99c40c8147080525ed1e3ba6d6ca07d42c5045ccf34889c5 languageName: node linkType: hard