mirror of https://github.com/abpframework/abp.git
43 changed files with 626 additions and 315 deletions
@ -1,10 +1,24 @@ |
|||||
import { Config } from '../models/config'; |
import { Config } from '../models/config'; |
||||
|
|
||||
|
/** |
||||
|
* @deprecated Use ConfigStateService. To be deleted in v5.0. |
||||
|
*/ |
||||
export class GetAppConfiguration { |
export class GetAppConfiguration { |
||||
static readonly type = '[Config] Get App Configuration'; |
static readonly type = '[Config] Get App Configuration'; |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* @deprecated Use EnvironmentService instead. To be deleted in v5.0. |
||||
|
*/ |
||||
export class SetEnvironment { |
export class SetEnvironment { |
||||
static readonly type = '[Config] Set Environment'; |
static readonly type = '[Config] Set Environment'; |
||||
constructor(public environment: Config.Environment) {} |
constructor(public environment: Config.Environment) {} |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* @deprecated Use EnvironmentService instead. To be deleted in v5.0. |
||||
|
*/ |
||||
|
export class PatchConfigState { |
||||
|
static readonly type = '[Config] Set State'; |
||||
|
constructor(public state: Config.State) {} |
||||
|
} |
||||
|
|||||
@ -0,0 +1,40 @@ |
|||||
|
import { AuthConfig } from 'angular-oauth2-oidc'; |
||||
|
import { ABP } from './common'; |
||||
|
|
||||
|
export interface Environment { |
||||
|
apis: Apis; |
||||
|
application: ApplicationInfo; |
||||
|
hmr?: boolean; |
||||
|
test?: boolean; |
||||
|
localization?: { defaultResourceName?: string }; |
||||
|
oAuthConfig: AuthConfig; |
||||
|
production: boolean; |
||||
|
remoteEnv?: RemoteEnv; |
||||
|
} |
||||
|
|
||||
|
export interface ApplicationInfo { |
||||
|
name: string; |
||||
|
baseUrl?: string; |
||||
|
logoUrl?: string; |
||||
|
} |
||||
|
|
||||
|
export type ApiConfig = { |
||||
|
[key: string]: string; |
||||
|
url: string; |
||||
|
} & Partial<{ |
||||
|
rootNamespace: string; |
||||
|
}>; |
||||
|
|
||||
|
export interface Apis { |
||||
|
[key: string]: ApiConfig; |
||||
|
default: ApiConfig; |
||||
|
} |
||||
|
|
||||
|
export type customMergeFn = (localEnv: Partial<Environment>, remoteEnv: any) => Environment; |
||||
|
|
||||
|
export interface RemoteEnv { |
||||
|
url: string; |
||||
|
mergeStrategy: 'deepmerge' | 'overwrite' | customMergeFn; |
||||
|
method?: string; |
||||
|
headers?: ABP.Dictionary<string>; |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
export interface LocalizationWithDefault { |
||||
|
key: string; |
||||
|
defaultValue: string; |
||||
|
} |
||||
|
|
||||
|
export type LocalizationParam = string | LocalizationWithDefault; |
||||
@ -1,35 +0,0 @@ |
|||||
import { Inject, Injectable, InjectionToken } from '@angular/core'; |
|
||||
import { |
|
||||
actionMatcher, |
|
||||
InitState, |
|
||||
NgxsNextPluginFn, |
|
||||
NgxsPlugin, |
|
||||
setValue, |
|
||||
UpdateState, |
|
||||
} from '@ngxs/store'; |
|
||||
import { ABP } from '../models/common'; |
|
||||
|
|
||||
export const NGXS_CONFIG_PLUGIN_OPTIONS = new InjectionToken('NGXS_CONFIG_PLUGIN_OPTIONS'); |
|
||||
|
|
||||
@Injectable() |
|
||||
export class ConfigPlugin implements NgxsPlugin { |
|
||||
private initialized = false; |
|
||||
|
|
||||
constructor(@Inject(NGXS_CONFIG_PLUGIN_OPTIONS) private options: ABP.Root) {} |
|
||||
|
|
||||
handle(state: any, event: any, next: NgxsNextPluginFn) { |
|
||||
const matches = actionMatcher(event); |
|
||||
const isInitAction = matches(InitState) || matches(UpdateState); |
|
||||
|
|
||||
if (isInitAction && !this.initialized) { |
|
||||
state = setValue(state, 'ConfigState', { |
|
||||
...(state.ConfigState && { ...state.ConfigState }), |
|
||||
...this.options, |
|
||||
}); |
|
||||
|
|
||||
this.initialized = true; |
|
||||
} |
|
||||
|
|
||||
return next(state, event); |
|
||||
} |
|
||||
} |
|
||||
@ -1 +0,0 @@ |
|||||
export * from './config.plugin'; |
|
||||
@ -1,68 +1,124 @@ |
|||||
import { Injectable } from '@angular/core'; |
import { Injectable } from '@angular/core'; |
||||
import { Store } from '@ngxs/store'; |
import { Observable } from 'rxjs'; |
||||
import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; |
import { map } from 'rxjs/operators'; |
||||
import { ConfigState } from '../states'; |
import { ApplicationConfiguration } from '../models/application-configuration'; |
||||
|
import { InternalStore } from '../utils/internal-store-utils'; |
||||
|
|
||||
@Injectable({ |
@Injectable({ |
||||
providedIn: 'root', |
providedIn: 'root', |
||||
}) |
}) |
||||
export class ConfigStateService { |
export class ConfigStateService { |
||||
constructor(private store: Store) {} |
private readonly store = new InternalStore({} as ApplicationConfiguration.Response); |
||||
|
|
||||
getAll() { |
get createOnUpdateStream() { |
||||
return this.store.selectSnapshot(ConfigState.getAll); |
return this.store.sliceUpdate; |
||||
} |
} |
||||
|
|
||||
getApplicationInfo() { |
setState = (state: ApplicationConfiguration.Response) => { |
||||
return this.store.selectSnapshot(ConfigState.getApplicationInfo); |
this.store.patch(state); |
||||
|
}; |
||||
|
|
||||
|
getOne$(key: string) { |
||||
|
return this.store.sliceState(state => state[key]); |
||||
} |
} |
||||
|
|
||||
getEnvironment() { |
getOne(key: string) { |
||||
return this.store.selectSnapshot(ConfigState.getEnvironment); |
return this.store.state[key]; |
||||
} |
} |
||||
|
|
||||
getOne(...args: Parameters<typeof ConfigState.getOne>) { |
getAll$(): Observable<ApplicationConfiguration.Response> { |
||||
return this.store.selectSnapshot(ConfigState.getOne(...args)); |
return this.store.sliceState(state => state); |
||||
} |
} |
||||
|
|
||||
getDeep(...args: Parameters<typeof ConfigState.getDeep>) { |
getAll(): ApplicationConfiguration.Response { |
||||
return this.store.selectSnapshot(ConfigState.getDeep(...args)); |
return this.store.state; |
||||
} |
} |
||||
|
|
||||
getApiUrl(...args: Parameters<typeof ConfigState.getApiUrl>) { |
getDeep$(keys: string[] | string) { |
||||
return this.store.selectSnapshot(ConfigState.getApiUrl(...args)); |
keys = splitKeys(keys); |
||||
|
|
||||
|
return this.store |
||||
|
.sliceState(state => state) |
||||
|
.pipe( |
||||
|
map(state => { |
||||
|
return (keys as string[]).reduce((acc, val) => { |
||||
|
if (acc) { |
||||
|
return acc[val]; |
||||
|
} |
||||
|
|
||||
|
return undefined; |
||||
|
}, state); |
||||
|
}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
getDeep(keys: string[] | string) { |
||||
|
keys = splitKeys(keys); |
||||
|
|
||||
|
return (keys as string[]).reduce((acc, val) => { |
||||
|
if (acc) { |
||||
|
return acc[val]; |
||||
|
} |
||||
|
|
||||
|
return undefined; |
||||
|
}, this.store.state); |
||||
} |
} |
||||
|
|
||||
getFeature(...args: Parameters<typeof ConfigState.getFeature>) { |
getFeature(key: string) { |
||||
return this.store.selectSnapshot(ConfigState.getFeature(...args)); |
return this.store.state.features?.values?.[key]; |
||||
} |
} |
||||
|
|
||||
getSetting(...args: Parameters<typeof ConfigState.getSetting>) { |
getFeature$(key: string) { |
||||
return this.store.selectSnapshot(ConfigState.getSetting(...args)); |
return this.store.sliceState(state => state.features?.values?.[key]); |
||||
} |
} |
||||
|
|
||||
getSettings(...args: Parameters<typeof ConfigState.getSettings>) { |
getSetting(key: string) { |
||||
return this.store.selectSnapshot(ConfigState.getSettings(...args)); |
return this.store.state.setting?.values?.[key]; |
||||
} |
} |
||||
|
|
||||
/** @deprecated Use PermissionService's getGrantedPolicySnapshot method */ |
getSetting$(key: string) { |
||||
getGrantedPolicy(...args: Parameters<typeof ConfigState.getGrantedPolicy>) { |
return this.store.sliceState(state => state.setting?.values?.[key]); |
||||
return this.store.selectSnapshot(ConfigState.getGrantedPolicy(...args)); |
|
||||
} |
} |
||||
|
|
||||
getLocalization(...args: Parameters<typeof ConfigState.getLocalization>) { |
getSettings(keyword?: string) { |
||||
return this.store.selectSnapshot(ConfigState.getLocalization(...args)); |
const settings = this.store.state.setting?.values || {}; |
||||
|
|
||||
|
if (!keyword) return settings; |
||||
|
|
||||
|
const keysFound = Object.keys(settings).filter(key => key.indexOf(keyword) > -1); |
||||
|
|
||||
|
return keysFound.reduce((acc, key) => { |
||||
|
acc[key] = settings[key]; |
||||
|
return acc; |
||||
|
}, {}); |
||||
} |
} |
||||
|
|
||||
getLocalizationResource(...args: Parameters<typeof ConfigState.getLocalizationResource>) { |
getSettings$(keyword?: string) { |
||||
return this.store.selectSnapshot(ConfigState.getLocalizationResource(...args)); |
return this.store |
||||
|
.sliceState(state => state.setting?.values) |
||||
|
.pipe( |
||||
|
map((settings = {}) => { |
||||
|
if (!keyword) return settings; |
||||
|
|
||||
|
const keysFound = Object.keys(settings).filter(key => key.indexOf(keyword) > -1); |
||||
|
|
||||
|
return keysFound.reduce((acc, key) => { |
||||
|
acc[key] = settings[key]; |
||||
|
return acc; |
||||
|
}, {}); |
||||
|
}), |
||||
|
); |
||||
} |
} |
||||
|
} |
||||
|
|
||||
dispatchGetAppConfiguration() { |
function splitKeys(keys: string[] | string): string[] { |
||||
return this.store.dispatch(new GetAppConfiguration()); |
if (typeof keys === 'string') { |
||||
|
keys = keys.split('.'); |
||||
} |
} |
||||
|
|
||||
dispatchSetEnvironment(...args: ConstructorParameters<typeof SetEnvironment>) { |
if (!Array.isArray(keys)) { |
||||
return this.store.dispatch(new SetEnvironment(...args)); |
throw new Error('The argument must be a dot string or an string array.'); |
||||
} |
} |
||||
|
|
||||
|
return keys; |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,36 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { Apis, Environment } from '../models/environment'; |
||||
|
import { InternalStore } from '../utils/internal-store-utils'; |
||||
|
|
||||
|
@Injectable({ providedIn: 'root' }) |
||||
|
export class EnvironmentService { |
||||
|
private readonly store = new InternalStore({} as Environment); |
||||
|
|
||||
|
get createOnUpdateStream() { |
||||
|
return this.store.sliceUpdate; |
||||
|
} |
||||
|
|
||||
|
getEnvironment$(): Observable<Environment> { |
||||
|
return this.store.sliceState(state => state); |
||||
|
} |
||||
|
|
||||
|
getEnvironment(): Environment { |
||||
|
return this.store.state; |
||||
|
} |
||||
|
|
||||
|
getApiUrl(key?: string) { |
||||
|
return (this.store.state.apis[key || 'default'] || this.store.state.apis.default).url; |
||||
|
} |
||||
|
|
||||
|
getApiUrl$(key?: string) { |
||||
|
return this.store |
||||
|
.sliceState(state => state.apis) |
||||
|
.pipe(map((apis: Apis) => (apis[key || 'default'] || apis.default).url)); |
||||
|
} |
||||
|
|
||||
|
setState(environment: Environment) { |
||||
|
this.store.patch(environment); |
||||
|
} |
||||
|
} |
||||
@ -1,42 +1,44 @@ |
|||||
import { HttpClient } from '@angular/common/http'; |
import { HttpClient } from '@angular/common/http'; |
||||
import { Injector } from '@angular/core'; |
import { Injector } from '@angular/core'; |
||||
import { Store } from '@ngxs/store'; |
import { Store } from '@ngxs/store'; |
||||
import { catchError, switchMap } from 'rxjs/operators'; |
import { catchError, tap } from 'rxjs/operators'; |
||||
import { SetEnvironment } from '../actions/config.actions'; |
|
||||
import { Config } from '../models/config'; |
|
||||
import { RestOccurError } from '../actions/rest.actions'; |
import { RestOccurError } from '../actions/rest.actions'; |
||||
|
import { Environment, RemoteEnv } from '../models/environment'; |
||||
|
import { EnvironmentService } from '../services/environment.service'; |
||||
import { deepMerge } from './object-utils'; |
import { deepMerge } from './object-utils'; |
||||
|
|
||||
export function getRemoteEnv(injector: Injector, environment: Partial<Config.Environment>) { |
export function getRemoteEnv(injector: Injector, environment: Partial<Environment>) { |
||||
|
const environmentService = injector.get(EnvironmentService); |
||||
|
|
||||
const { remoteEnv } = environment; |
const { remoteEnv } = environment; |
||||
const { headers = {}, method = 'GET', url } = remoteEnv || ({} as Config.RemoteEnv); |
const { headers = {}, method = 'GET', url } = remoteEnv || ({} as RemoteEnv); |
||||
if (!url) return Promise.resolve(); |
if (!url) return Promise.resolve(); |
||||
|
|
||||
const http = injector.get(HttpClient); |
const http = injector.get(HttpClient); |
||||
const store = injector.get(Store); |
const store = injector.get(Store); |
||||
|
|
||||
return http |
return http |
||||
.request<Config.Environment>(method, url, { headers }) |
.request<Environment>(method, url, { headers }) |
||||
.pipe( |
.pipe( |
||||
catchError(err => store.dispatch(new RestOccurError(err))), // TODO: Condiser get handle function from a provider
|
catchError(err => store.dispatch(new RestOccurError(err))), // TODO: Condiser get handle function from a provider
|
||||
switchMap(env => store.dispatch(mergeEnvironments(environment, env, remoteEnv))), |
tap(env => environmentService.setState(mergeEnvironments(environment, env, remoteEnv))), |
||||
) |
) |
||||
.toPromise(); |
.toPromise(); |
||||
} |
} |
||||
|
|
||||
function mergeEnvironments( |
function mergeEnvironments( |
||||
local: Partial<Config.Environment>, |
local: Partial<Environment>, |
||||
remote: any, |
remote: any, |
||||
config: Config.RemoteEnv, |
config: RemoteEnv, |
||||
) { |
): Environment { |
||||
switch (config.mergeStrategy) { |
switch (config.mergeStrategy) { |
||||
case 'deepmerge': |
case 'deepmerge': |
||||
return new SetEnvironment(deepMerge(local, remote)); |
return deepMerge(local, remote); |
||||
case 'overwrite': |
case 'overwrite': |
||||
case null: |
case null: |
||||
case undefined: |
case undefined: |
||||
return new SetEnvironment(remote); |
return remote; |
||||
default: |
default: |
||||
return new SetEnvironment(config.mergeStrategy(local, remote)); |
return config.mergeStrategy(local, remote); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue