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'; |
|||
|
|||
/** |
|||
* @deprecated Use ConfigStateService. To be deleted in v5.0. |
|||
*/ |
|||
export class GetAppConfiguration { |
|||
static readonly type = '[Config] Get App Configuration'; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated Use EnvironmentService instead. To be deleted in v5.0. |
|||
*/ |
|||
export class SetEnvironment { |
|||
static readonly type = '[Config] Set 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 { Store } from '@ngxs/store'; |
|||
import { GetAppConfiguration, SetEnvironment } from '../actions/config.actions'; |
|||
import { ConfigState } from '../states'; |
|||
import { Observable } from 'rxjs'; |
|||
import { map } from 'rxjs/operators'; |
|||
import { ApplicationConfiguration } from '../models/application-configuration'; |
|||
import { InternalStore } from '../utils/internal-store-utils'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class ConfigStateService { |
|||
constructor(private store: Store) {} |
|||
private readonly store = new InternalStore({} as ApplicationConfiguration.Response); |
|||
|
|||
getAll() { |
|||
return this.store.selectSnapshot(ConfigState.getAll); |
|||
get createOnUpdateStream() { |
|||
return this.store.sliceUpdate; |
|||
} |
|||
|
|||
getApplicationInfo() { |
|||
return this.store.selectSnapshot(ConfigState.getApplicationInfo); |
|||
setState = (state: ApplicationConfiguration.Response) => { |
|||
this.store.patch(state); |
|||
}; |
|||
|
|||
getOne$(key: string) { |
|||
return this.store.sliceState(state => state[key]); |
|||
} |
|||
|
|||
getEnvironment() { |
|||
return this.store.selectSnapshot(ConfigState.getEnvironment); |
|||
getOne(key: string) { |
|||
return this.store.state[key]; |
|||
} |
|||
|
|||
getOne(...args: Parameters<typeof ConfigState.getOne>) { |
|||
return this.store.selectSnapshot(ConfigState.getOne(...args)); |
|||
getAll$(): Observable<ApplicationConfiguration.Response> { |
|||
return this.store.sliceState(state => state); |
|||
} |
|||
|
|||
getDeep(...args: Parameters<typeof ConfigState.getDeep>) { |
|||
return this.store.selectSnapshot(ConfigState.getDeep(...args)); |
|||
getAll(): ApplicationConfiguration.Response { |
|||
return this.store.state; |
|||
} |
|||
|
|||
getApiUrl(...args: Parameters<typeof ConfigState.getApiUrl>) { |
|||
return this.store.selectSnapshot(ConfigState.getApiUrl(...args)); |
|||
getDeep$(keys: string[] | string) { |
|||
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>) { |
|||
return this.store.selectSnapshot(ConfigState.getFeature(...args)); |
|||
getFeature(key: string) { |
|||
return this.store.state.features?.values?.[key]; |
|||
} |
|||
|
|||
getSetting(...args: Parameters<typeof ConfigState.getSetting>) { |
|||
return this.store.selectSnapshot(ConfigState.getSetting(...args)); |
|||
getFeature$(key: string) { |
|||
return this.store.sliceState(state => state.features?.values?.[key]); |
|||
} |
|||
|
|||
getSettings(...args: Parameters<typeof ConfigState.getSettings>) { |
|||
return this.store.selectSnapshot(ConfigState.getSettings(...args)); |
|||
getSetting(key: string) { |
|||
return this.store.state.setting?.values?.[key]; |
|||
} |
|||
|
|||
/** @deprecated Use PermissionService's getGrantedPolicySnapshot method */ |
|||
getGrantedPolicy(...args: Parameters<typeof ConfigState.getGrantedPolicy>) { |
|||
return this.store.selectSnapshot(ConfigState.getGrantedPolicy(...args)); |
|||
getSetting$(key: string) { |
|||
return this.store.sliceState(state => state.setting?.values?.[key]); |
|||
} |
|||
|
|||
getLocalization(...args: Parameters<typeof ConfigState.getLocalization>) { |
|||
return this.store.selectSnapshot(ConfigState.getLocalization(...args)); |
|||
getSettings(keyword?: string) { |
|||
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>) { |
|||
return this.store.selectSnapshot(ConfigState.getLocalizationResource(...args)); |
|||
getSettings$(keyword?: string) { |
|||
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() { |
|||
return this.store.dispatch(new GetAppConfiguration()); |
|||
function splitKeys(keys: string[] | string): string[] { |
|||
if (typeof keys === 'string') { |
|||
keys = keys.split('.'); |
|||
} |
|||
|
|||
dispatchSetEnvironment(...args: ConstructorParameters<typeof SetEnvironment>) { |
|||
return this.store.dispatch(new SetEnvironment(...args)); |
|||
if (!Array.isArray(keys)) { |
|||
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 { Injector } from '@angular/core'; |
|||
import { Store } from '@ngxs/store'; |
|||
import { catchError, switchMap } from 'rxjs/operators'; |
|||
import { SetEnvironment } from '../actions/config.actions'; |
|||
import { Config } from '../models/config'; |
|||
import { catchError, tap } from 'rxjs/operators'; |
|||
import { RestOccurError } from '../actions/rest.actions'; |
|||
import { Environment, RemoteEnv } from '../models/environment'; |
|||
import { EnvironmentService } from '../services/environment.service'; |
|||
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 { headers = {}, method = 'GET', url } = remoteEnv || ({} as Config.RemoteEnv); |
|||
const { headers = {}, method = 'GET', url } = remoteEnv || ({} as RemoteEnv); |
|||
if (!url) return Promise.resolve(); |
|||
|
|||
const http = injector.get(HttpClient); |
|||
const store = injector.get(Store); |
|||
|
|||
return http |
|||
.request<Config.Environment>(method, url, { headers }) |
|||
.request<Environment>(method, url, { headers }) |
|||
.pipe( |
|||
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(); |
|||
} |
|||
|
|||
function mergeEnvironments( |
|||
local: Partial<Config.Environment>, |
|||
local: Partial<Environment>, |
|||
remote: any, |
|||
config: Config.RemoteEnv, |
|||
) { |
|||
config: RemoteEnv, |
|||
): Environment { |
|||
switch (config.mergeStrategy) { |
|||
case 'deepmerge': |
|||
return new SetEnvironment(deepMerge(local, remote)); |
|||
return deepMerge(local, remote); |
|||
case 'overwrite': |
|||
case null: |
|||
case undefined: |
|||
return new SetEnvironment(remote); |
|||
return remote; |
|||
default: |
|||
return new SetEnvironment(config.mergeStrategy(local, remote)); |
|||
return config.mergeStrategy(local, remote); |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue