Browse Source

Merge pull request #4956 from abpframework/feat/4894

Added support for the Domain Tenant Resolver
pull/4968/head
Mehmet Erim 6 years ago
committed by GitHub
parent
commit
07bc777e97
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/en/Multi-Tenancy.md
  2. 135
      docs/en/UI/Angular/Multi-Tenancy.md
  3. 2
      docs/en/UI/Angular/Permission-Management.md
  4. BIN
      docs/en/UI/Angular/images/tenant-switching-box.png
  5. BIN
      docs/en/UI/Angular/images/tenants-page.png
  6. 6
      docs/en/docs-nav.json
  7. 6
      npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts
  8. 6
      npm/ng-packs/apps/dev-app/src/environments/environment.ts
  9. 2
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
  10. 8
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
  11. 9
      npm/ng-packs/packages/core/src/lib/core.module.ts
  12. 1
      npm/ng-packs/packages/core/src/lib/handlers/index.ts
  13. 35
      npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts
  14. 1
      npm/ng-packs/packages/core/src/lib/models/config.ts
  15. 14
      npm/ng-packs/packages/core/src/lib/models/find-tenant-result-dto.ts
  16. 1
      npm/ng-packs/packages/core/src/lib/models/index.ts
  17. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  18. 45
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts
  19. 4
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  20. 39
      npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts
  21. 103
      npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts
  22. 2
      npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts
  23. 22
      npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts
  24. 2
      npm/ng-packs/packages/core/src/lib/utils/index.ts
  25. 17
      npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts
  26. 74
      npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts
  27. 17
      npm/ng-packs/packages/core/src/lib/utils/string-utils.ts
  28. 3
      npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts
  29. 3
      npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts
  30. 586
      npm/ng-packs/yarn.lock
  31. 6
      templates/app/angular/src/environments/environment.prod.ts
  32. 5
      templates/app/angular/src/environments/environment.ts
  33. 6
      templates/module/angular/projects/dev-app/src/environments/environment.prod.ts
  34. 6
      templates/module/angular/projects/dev-app/src/environments/environment.ts

2
docs/en/Multi-Tenancy.md

@ -355,7 +355,7 @@ namespace MyCompany.MyProject
}
````
{0} is the the placeholder to determine current tenant's unique name.
{0} is the placeholder to determine current tenant's unique name.
Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut:

135
docs/en/UI/Angular/Multi-Tenancy.md

@ -0,0 +1,135 @@
# Multi Tenancy in Angular UI
ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates.
![Tenants Page](./images/tenants-page.png)
<p style="font-size:small;text-align:center;">Tenants page</p>
On the page above, you can;
- See the all tenants.
- Create a new tenant.
- Edit an existing tenant.
- Delete a tenant.
![Tenant Switching Component](./images/tenant-switching-box.png)
<p style="font-size:small;text-align:center;">Tenant Switching Component</p>
You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id sends to the backend as `__tenant` header on each request.
## Domain Tenant Resolver
Angular UI can get the tenant name from the app running URL. You can determine the current tenant by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). To do this, you need to set the `application.baseUrl` property in the environment:
Subdomain resolver:
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/'
},
//...
}
```
**{0}** is the placeholder to determine current tenant's unique name.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get the tenant name as **mytenant1**. Then, the app will call the `/api/abp/multi-tenancy/tenants/by-name/mytenant1` endpoint to check if the tenant exists. If the tenant (mytenant1) exists, the app will keep this tenant data and send its `id` as `__tenant` header to the backend on each request. If the tenant does not exist, the app will not send `__tenant` header to the backend.
> **Important Note:** If you define the `baseUrl` with the placeholder (**{0}**), the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page) will be hidden.
Domain resolver:
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.com/'
},
//...
}
```
After the configuration above, if your app runs on the `mytenant.com`, the app will get the tenant name as **mytenant**.
### Tenant Specific Remote Endpoints
The **{0}** placeholder can be put to the API URLs in the environment to determine tenant specific endpoints.
```js
// environment.prod.ts
export const environment = {
//...
application: {
baseUrl: 'https://{0}.mydomain.com/',
//...
},
oAuthConfig: {
issuer: 'https://{0}.ids.mydomain.com',
//...
},
apis: {
default: {
url: 'https://{0}.api.mydomain.com',
},
AbpIdentity: {
url: 'https://{0}.identity.mydomain.com',
},
},
}
```
> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional.
After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows:
```js
// environment object in ConfigState
{
//...
application: {
baseUrl: 'https://mytenant1.mydomain.com/',
//...
},
oAuthConfig: {
issuer: 'https://mytenant1.ids.mydomain.com',
//...
},
apis: {
default: {
url: 'https://mytenant1.api.mydomain.com',
},
AbpIdentity: {
url: 'https://mytenant1.identity.mydomain.com',
},
},
}
```
After this replacement, the app will use the following URLs:
- `https://mytenant1.ids.mydomain.com` as IdentityServer URL.
- `https://mytenant1.api.mydomain.com` as default URL.
- `https://mytenant1.identity.mydomain.com` as `AbpIdentity` remote endpoint URL.
The app sends the `__tenant` header that contains the current tenant id on each request.
## See Also
* [Multi Tenancy in ABP](../../Multi-Tenancy.md)
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)

2
docs/en/UI/Angular/Permission-Management.md

@ -78,4 +78,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)
* [Multi Tenancy](./Multi-Tenancy.md)

BIN
docs/en/UI/Angular/images/tenant-switching-box.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/UI/Angular/images/tenants-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

6
docs/en/docs-nav.json

@ -262,7 +262,7 @@
"path": "Blob-Storing-Custom-Provider.md"
}
]
}
}
]
},
{
@ -435,6 +435,10 @@
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Multi Tenancy",
"path": "UI/Angular/Multi-Tenancy.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"

6
npm/ng-packs/apps/dev-app/src/environments/environment.prod.ts

@ -1,7 +1,10 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: true,
hmr: false,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -10,7 +13,6 @@ export const environment = {
clientId: 'MyProjectName_App',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true,
},
@ -19,4 +21,4 @@ export const environment = {
url: 'https://localhost:44305',
},
},
};
} as Config.Environment;

6
npm/ng-packs/apps/dev-app/src/environments/environment.ts

@ -1,7 +1,10 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: false,
hmr: false,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -10,7 +13,6 @@ export const environment = {
clientId: 'MyProjectName_App',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true,
},
@ -19,4 +21,4 @@ export const environment = {
url: 'https://localhost:44305',
},
},
};
} as Config.Environment;

2
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html

@ -1,6 +1,6 @@
<div class="row">
<div class="mx-auto col col-md-5">
<ng-container *ngIf="isMultiTenancyEnabled$ | async">
<ng-container *ngIf="(isMultiTenancyEnabled$ | async) && multiTenancy.isTenantBoxVisible">
<abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box>
</ng-container>

8
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts

@ -1,4 +1,4 @@
import { ConfigState, SubscriptionService } from '@abp/ng.core';
import { ConfigState, SubscriptionService, MultiTenancyService } from '@abp/ng.core';
import { Component, Input, OnInit, TemplateRef } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
@ -26,7 +26,11 @@ export class AuthWrapperComponent
tenantBoxKey = eAccountComponents.TenantBox;
constructor(private store: Store, private subscription: SubscriptionService) {}
constructor(
public readonly multiTenancy: MultiTenancyService,
private store: Store,
private subscription: SubscriptionService,
) {}
ngOnInit() {
this.subscription.addOne(

9
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -21,6 +21,7 @@ import { PermissionDirective } from './directives/permission.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
import { StopPropagationDirective } from './directives/stop-propagation.directive';
import { VisibilityDirective } from './directives/visibility.directive';
import { OAuthConfigurationHandler } from './handlers/oauth-configuration.handler';
import { RoutesHandler } from './handlers/routes.handler';
import { ApiInterceptor } from './interceptors/api.interceptor';
import { LocalizationModule } from './localization.module';
@ -34,10 +35,10 @@ import { ConfigState } from './states/config.state';
import { ProfileState } from './states/profile.state';
import { ReplaceableComponentsState } from './states/replaceable-components.state';
import { SessionState } from './states/session.state';
import { CORE_OPTIONS, coreOptionsFactory } from './tokens/options.token';
import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token';
import { noop } from './utils/common-utils';
import './utils/date-extensions';
import { getInitialData, localeInitializer, configureOAuth } from './utils/initial-utils';
import { getInitialData, localeInitializer } from './utils/initial-utils';
export function storageFactory(): OAuthStorage {
return localStorage;
@ -187,8 +188,8 @@ export class CoreModule {
{
provide: APP_INITIALIZER,
multi: true,
deps: [Injector, NGXS_CONFIG_PLUGIN_OPTIONS],
useFactory: configureOAuth,
deps: [OAuthConfigurationHandler],
useFactory: noop,
},
{
provide: APP_INITIALIZER,

1
npm/ng-packs/packages/core/src/lib/handlers/index.ts

@ -1 +1,2 @@
export * from './oauth-configuration.handler';
export * from './routes.handler';

35
npm/ng-packs/packages/core/src/lib/handlers/oauth-configuration.handler.ts

@ -0,0 +1,35 @@
import { Inject, Injectable } from '@angular/core';
import { Actions, ofActionSuccessful } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import compare from 'just-compare';
import { filter, map } from 'rxjs/operators';
import { SetEnvironment } from '../actions/config.actions';
import { ABP } from '../models/common';
import { CORE_OPTIONS } from '../tokens/options.token';
@Injectable({
providedIn: 'root',
})
export class OAuthConfigurationHandler {
constructor(
private actions: Actions,
private oAuthService: OAuthService,
@Inject(CORE_OPTIONS) private options: ABP.Root,
) {
this.oAuthService.configure(this.options.environment.oAuthConfig);
this.listenToSetEnvironment();
}
private listenToSetEnvironment() {
this.actions
.pipe(ofActionSuccessful(SetEnvironment))
.pipe(
map(({ environment }: SetEnvironment) => environment.oAuthConfig),
filter(config => !compare(config, this.options.environment.oAuthConfig)),
)
.subscribe(config => {
this.oAuthService.configure(config);
});
}
}

1
npm/ng-packs/packages/core/src/lib/models/config.ts

@ -17,6 +17,7 @@ export namespace Config {
export interface Application {
name: string;
baseUrl?: string;
logoUrl?: string;
}

14
npm/ng-packs/packages/core/src/lib/models/find-tenant-result-dto.ts

@ -0,0 +1,14 @@
export class FindTenantResultDto {
success: boolean;
tenantId?: string;
name: string;
constructor(initialValues: Partial<FindTenantResultDto> = {}) {
for (const key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
}
}

1
npm/ng-packs/packages/core/src/lib/models/index.ts

@ -2,6 +2,7 @@ export * from './application-configuration';
export * from './common';
export * from './config';
export * from './dtos';
export * from './find-tenant-result-dto';
export * from './profile';
export * from './replaceable-components';
export * from './rest';

1
npm/ng-packs/packages/core/src/lib/services/index.ts

@ -6,6 +6,7 @@ export * from './dom-insertion.service';
export * from './lazy-load.service';
export * from './list.service';
export * from './localization.service';
export * from './multi-tenancy.service';
export * from './profile-state.service';
export * from './profile.service';
export * from './rest.service';

45
npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { SetTenant } from '../actions/session.actions';
import { ABP } from '../models/common';
import { FindTenantResultDto } from '../models/find-tenant-result-dto';
import { RestService } from './rest.service';
@Injectable({ providedIn: 'root' })
export class MultiTenancyService {
private _domainTenant: ABP.BasicItem = null;
set domainTenant(value: ABP.BasicItem) {
this._domainTenant = value;
this.store.dispatch(new SetTenant(value));
}
get domainTenant() {
return this._domainTenant;
}
isTenantBoxVisible = true;
apiName = 'abp';
constructor(private restService: RestService, private store: Store) {}
findTenantByName(name: string, headers: ABP.Dictionary<string>): Observable<FindTenantResultDto> {
return this.restService.request(
{
url: `/api/abp/multi-tenancy/tenants/by-name/${name}`,
method: 'GET',
headers,
},
{ apiName: this.apiName },
);
}
findTenantById(id: string, headers: ABP.Dictionary<string>): Observable<FindTenantResultDto> {
return this.restService.request(
{ url: `/api/abp/multi-tenancy/tenants/by-id/${id}`, method: 'GET', headers },
{ apiName: this.apiName },
);
}
}

4
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -233,8 +233,6 @@ export class ConfigState {
@Action(SetEnvironment)
setEnvironment({ patchState }: StateContext<Config.State>, { environment }: SetEnvironment) {
return patchState({
environment,
});
return patchState({ environment });
}
}

39
npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts

@ -1,10 +1,12 @@
import { Component, Injector } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { of } from 'rxjs';
import { GetAppConfiguration } from '../actions';
import { getInitialData, localeInitializer, configureOAuth } from '../utils';
import { OAuthService } from 'angular-oauth2-oidc';
import { CORE_OPTIONS } from '../tokens/options.token';
import { checkAccessToken, getInitialData, localeInitializer } from '../utils';
import * as multiTenancyUtils from '../utils/multi-tenancy-utils';
@Component({
selector: 'abp-dummy',
@ -17,30 +19,21 @@ describe('InitialUtils', () => {
const createComponent = createComponentFactory({
component: DummyComponent,
mocks: [Store, OAuthService],
providers: [
{ provide: CORE_OPTIONS, useValue: { environment: { oAuthConfig: { issuer: 'test' } } } },
],
});
beforeEach(() => (spectator = createComponent()));
describe('#configureOAuth', () => {
test('should be called the the configure method of OAuthService', async () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const oAuth = spectator.inject(OAuthService);
const configureSpy = jest.spyOn(oAuth, 'configure');
injectorSpy.mockReturnValueOnce(oAuth);
await configureOAuth(injector, { environment: { oAuthConfig: { issuer: 'test' } } })();
expect(configureSpy).toHaveBeenCalledWith({ issuer: 'test' });
});
});
describe('#getInitialData', () => {
test('should dispatch GetAppConfiguration and return', async () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const store = spectator.inject(Store);
const dispatchSpy = jest.spyOn(store, 'dispatch');
const parseTenantFromUrlSpy = jest.spyOn(multiTenancyUtils, 'parseTenantFromUrl');
parseTenantFromUrlSpy.mockReturnValue(Promise.resolve());
injectorSpy.mockReturnValueOnce(store);
injectorSpy.mockReturnValueOnce({ skipGetAppConfiguration: false });
@ -57,16 +50,16 @@ describe('InitialUtils', () => {
test('should call logOut fn of OAuthService when token is valid and current user not found', async () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const store = spectator.inject(Store);
const dispatchSpy = jest.spyOn(store, 'dispatch');
const logOutFn = jest.fn();
injectorSpy.mockReturnValueOnce(store);
injectorSpy.mockReturnValueOnce({ skipGetAppConfiguration: false });
injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => true, logOut: logOutFn });
dispatchSpy.mockReturnValue(of({ currentUser: { id: null } }));
injectorSpy.mockReturnValue({ hasValidAccessToken: () => true, logOut: logOutFn });
getInitialData(injector)();
checkAccessToken(
{
selectSnapshot: () => false,
} as any,
injector,
);
expect(logOutFn).toHaveBeenCalled();
});
});

103
npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts

@ -0,0 +1,103 @@
import { Component, Injector } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import clone from 'just-clone';
import { BehaviorSubject } from 'rxjs';
import { FindTenantResultDto } from '../models/find-tenant-result-dto';
import { MultiTenancyService } from '../services/multi-tenancy.service';
import { parseTenantFromUrl } from '../utils';
const environment = {
production: false,
hmr: false,
application: {
baseUrl: 'https://{0}.volosoft.com',
name: 'MyProjectName',
logoUrl: '',
},
oAuthConfig: {
issuer: 'https://{0}.api.volosoft.com',
clientId: 'MyProjectName_App',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
oidc: false,
requireHttps: true,
},
apis: {
default: {
url: 'https://{0}.api.volosoft.com',
},
abp: {
url: 'https://api.volosoft.com/{0}',
},
},
};
const setHref = url => {
global.window = Object.create(window);
delete window.location;
Object.defineProperty(window, 'location', {
value: {
href: url,
},
});
};
@Component({
selector: 'abp-dummy',
template: '',
})
export class DummyComponent {}
describe('MultiTenancyUtils', () => {
let spectator: Spectator<DummyComponent>;
const createComponent = createComponentFactory({
component: DummyComponent,
mocks: [Store, MultiTenancyService],
});
beforeEach(() => (spectator = createComponent()));
describe('#parseTenantFromUrl', () => {
test('should get the tenancyName, set replaced environment and call the findTenantByName method of MultiTenancyService', async () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const store = spectator.inject(Store);
const selectSnapshotSpy = jest.spyOn(store, 'selectSnapshot');
const dispatchSpy = jest.spyOn(store, 'dispatch');
const multiTenancyService = spectator.inject(MultiTenancyService);
const findTenantByNameSpy = jest.spyOn(multiTenancyService, 'findTenantByName');
injectorSpy.mockReturnValueOnce(spectator.inject(Store));
injectorSpy.mockReturnValueOnce(multiTenancyService);
selectSnapshotSpy.mockReturnValue(clone(environment));
setHref('https://abp.volosoft.com/');
dispatchSpy.mockReturnValue(new BehaviorSubject(true));
findTenantByNameSpy.mockReturnValue(
new BehaviorSubject({ name: 'abp', tenantId: '1', success: true } as FindTenantResultDto),
);
parseTenantFromUrl(injector);
const replacedEnv = {
...environment,
application: { ...environment.application, baseUrl: 'https://abp.volosoft.com' },
oAuthConfig: { ...environment.oAuthConfig, issuer: 'https://abp.api.volosoft.com' },
apis: {
default: {
url: 'https://abp.api.volosoft.com',
},
abp: {
url: 'https://api.volosoft.com/abp',
},
},
};
expect(dispatchSpy).toHaveBeenCalledWith({ environment: replacedEnv });
expect(findTenantByNameSpy).toHaveBeenCalledWith('abp', { __tenant: '' });
expect(multiTenancyService.domainTenant).toEqual({ id: '1', name: 'abp' });
});
});
});

2
npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts

@ -6,6 +6,7 @@ import { Rest } from '../models';
import { RestService } from '../services/rest.service';
import { ConfigState } from '../states/config.state';
import { CORE_OPTIONS } from '../tokens';
import { OAuthService } from 'angular-oauth2-oidc';
describe('HttpClient testing', () => {
let spectator: SpectatorHttp<RestService>;
@ -16,6 +17,7 @@ describe('HttpClient testing', () => {
dataService: RestService,
imports: [NgxsModule.forRoot([ConfigState])],
providers: [{ provide: CORE_OPTIONS, useValue: { environment: {} } }],
mocks: [OAuthService],
});
beforeEach(() => {

22
npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts

@ -0,0 +1,22 @@
import { createTokenParser } from '../utils/string-utils';
describe('String Utils', () => {
describe('#createTokenParser', () => {
const parseTokens = createTokenParser('{subDomain}.{domain}.{gtld}|{domain}.{gtld}');
test.each`
url | subDomain | domain | gtld
${'www.example.com'} | ${'www'} | ${'example'} | ${'com'}
${'test.sub.example.com'} | ${'test.sub'} | ${'example'} | ${'com'}
${'example.com'} | ${undefined} | ${'example'} | ${'com'}
`(
'should return subDomain as $subDomain, domain as $domain, and gtld as $gtld when url is $url',
({ url, subDomain, domain, gtld }) => {
const parsed = parseTokens(url);
expect(parsed.subDomain[0]).toBe(subDomain);
expect(parsed.domain[0]).toBe(domain);
expect(parsed.gtld[0]).toBe(gtld);
},
);
});
});

2
npm/ng-packs/packages/core/src/lib/utils/index.ts

@ -6,7 +6,9 @@ export * from './generator-utils';
export * from './initial-utils';
export * from './lazy-load-utils';
export * from './localization-utils';
export * from './multi-tenancy-utils';
export * from './number-utils';
export * from './route-utils';
export * from './rxjs-utils';
export * from './string-utils';
export * from './tree-utils';

17
npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts

@ -7,22 +7,15 @@ import { GetAppConfiguration } from '../actions/config.actions';
import { ABP } from '../models/common';
import { ConfigState } from '../states/config.state';
import { CORE_OPTIONS } from '../tokens/options.token';
export function configureOAuth(injector: Injector, options: ABP.Root) {
const fn = () => {
const oAuth = injector.get(OAuthService);
oAuth.configure(options.environment.oAuthConfig);
return Promise.resolve();
};
return fn;
}
import { parseTenantFromUrl } from './multi-tenancy-utils';
export function getInitialData(injector: Injector) {
const fn = () => {
const fn = async () => {
const store: Store = injector.get(Store);
const { skipGetAppConfiguration } = injector.get(CORE_OPTIONS) as ABP.Root;
await parseTenantFromUrl(injector);
if (skipGetAppConfiguration) return;
return store
@ -34,7 +27,7 @@ export function getInitialData(injector: Injector) {
return fn;
}
function checkAccessToken(store: Store, injector: Injector) {
export function checkAccessToken(store: Store, injector: Injector) {
const oAuth = injector.get(OAuthService);
if (oAuth.hasValidAccessToken() && !store.selectSnapshot(ConfigState.getDeep('currentUser.id'))) {
oAuth.logOut();

74
npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts

@ -0,0 +1,74 @@
import { Injector } from '@angular/core';
import { Store } from '@ngxs/store';
import clone from 'just-clone';
import { switchMap, tap } from 'rxjs/operators';
import { SetEnvironment } from '../actions';
import { Config } from '../models/config';
import { MultiTenancyService } from '../services/multi-tenancy.service';
import { ConfigState } from '../states/config.state';
import { createTokenParser } from './string-utils';
const tenancyPlaceholder = '{0}';
function getCurrentTenancyName(appBaseUrl: string): string {
if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/';
const parseTokens = createTokenParser(appBaseUrl);
const token = tenancyPlaceholder.replace(/[}{]/g, '');
return parseTokens(window.location.href)[token][0];
}
export async function parseTenantFromUrl(injector: Injector) {
const store: Store = injector.get(Store);
const multiTenancyService = injector.get(MultiTenancyService);
const environment = store.selectSnapshot(ConfigState.getOne('environment')) as Config.Environment;
const { baseUrl = '' } = environment.application;
const tenancyName = getCurrentTenancyName(baseUrl);
if (tenancyName) {
multiTenancyService.isTenantBoxVisible = false;
return setEnvironment(store, tenancyName)
.pipe(
switchMap(() => multiTenancyService.findTenantByName(tenancyName, { __tenant: '' })),
tap(res => {
multiTenancyService.domainTenant = res.success
? { id: res.tenantId, name: res.name }
: null;
}),
)
.toPromise();
}
return Promise.resolve();
}
function setEnvironment(store: Store, tenancyName: string) {
const environment = clone(
store.selectSnapshot(ConfigState.getOne('environment')),
) as Config.Environment;
if (environment.application.baseUrl) {
environment.application.baseUrl = environment.application.baseUrl.replace(
tenancyPlaceholder,
tenancyName,
);
}
environment.oAuthConfig.issuer = environment.oAuthConfig.issuer.replace(
tenancyPlaceholder,
tenancyName,
);
Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = environment.apis[api][key].replace(
tenancyPlaceholder,
tenancyName,
);
});
});
return store.dispatch(new SetEnvironment(environment));
}

17
npm/ng-packs/packages/core/src/lib/utils/string-utils.ts

@ -0,0 +1,17 @@
export function createTokenParser(format: string) {
return (string: string) => {
const tokens: string[] = [];
const regex = format.replace(/\./g, '\\.').replace(/\{\s?([0-9a-zA-Z]+)\s?\}/g, (_, token) => {
tokens.push(token);
return '(.+)';
});
const matches = (string.match(regex) || []).slice(1);
return matches.reduce((acc, v, i) => {
const key = tokens[i];
acc[key] = [...(acc[key] || []), v].filter(Boolean);
return acc;
}, {} as Record<string, string[]>);
};
}

3
npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts

@ -13,6 +13,7 @@ import {
getObjectExtensionEntitiesFromStore,
mapEntitiesToContributors,
} from '../lib/utils/state.util';
import { OAuthService } from 'angular-oauth2-oidc';
describe('State Utils', () => {
let spectator: SpectatorService<Store>;
@ -21,7 +22,7 @@ describe('State Utils', () => {
const createService = createServiceFactory({
service: Store,
imports: [NgxsModule.forRoot([ConfigState])],
mocks: [HttpClient],
mocks: [HttpClient, OAuthService],
});
beforeEach(() => {

3
npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts

@ -6,6 +6,7 @@ import { NgxsModule, Store } from '@ngxs/store';
import { HttpClient } from '@angular/common/http';
import { getPasswordValidators } from '../utils';
import { Validators } from '@angular/forms';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({ template: '', selector: 'abp-dummy' })
class DummyComponent {}
@ -15,7 +16,7 @@ describe('ValidationUtils', () => {
const createComponent = createComponentFactory({
component: DummyComponent,
imports: [NgxsModule.forRoot([ConfigState]), NgxValidateCoreModule.forRoot()],
mocks: [HttpClient],
mocks: [HttpClient, OAuthService],
});
beforeEach(() => (spectator = createComponent()));

586
npm/ng-packs/yarn.lock

File diff suppressed because it is too large

6
templates/app/angular/src/environments/environment.prod.ts

@ -1,6 +1,9 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: true,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -9,7 +12,6 @@ export const environment = {
clientId: 'MyProjectName_App',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true,
},
@ -21,4 +23,4 @@ export const environment = {
localization: {
defaultResourceName: 'MyProjectName',
},
};
} as Config.Environment;

5
templates/app/angular/src/environments/environment.ts

@ -1,6 +1,9 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: false,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -21,4 +24,4 @@ export const environment = {
localization: {
defaultResourceName: 'MyProjectName',
},
};
} as Config.Environment;

6
templates/module/angular/projects/dev-app/src/environments/environment.prod.ts

@ -1,6 +1,9 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: true,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -9,7 +12,6 @@ export const environment = {
clientId: 'MyProjectName_ConsoleTestApp',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true,
},
@ -21,4 +23,4 @@ export const environment = {
url: 'https://localhost:44300',
},
},
};
} as Config.Environment;

6
templates/module/angular/projects/dev-app/src/environments/environment.ts

@ -1,6 +1,9 @@
import { Config } from '@abp/ng.core';
export const environment = {
production: false,
application: {
baseUrl: 'http://localhost:4200/',
name: 'MyProjectName',
logoUrl: '',
},
@ -9,7 +12,6 @@ export const environment = {
clientId: 'MyProjectName_ConsoleTestApp',
dummyClientSecret: '1q2w3e*',
scope: 'MyProjectName',
showDebugInformation: true,
oidc: false,
requireHttps: true,
},
@ -21,4 +23,4 @@ export const environment = {
url: 'https://localhost:44300',
},
},
};
} as Config.Environment;

Loading…
Cancel
Save