diff --git a/PLAYBOOK-NEST.md b/PLAYBOOK-NEST.md index 3527c1ec1..2cb3abe0a 100644 --- a/PLAYBOOK-NEST.md +++ b/PLAYBOOK-NEST.md @@ -97,6 +97,22 @@ nest g controller chat app/chat --flat --dry-run nest g service chat app/chat --flat --dry-run nest g gateway chat app/chat --flat --dry-run +# scaffold external module +nest g module app/external --dry-run +nest g controller weather app/external --dry-run +nest g service weather app/external --dry-run + +# scaffold project module +nest g module app/project --dry-run +nest g controller kubernetes app/project --dry-run +nest g service kubernetes app/project --dry-run +nest g class cluster/cluster.entity app/project --no-spec --dry-run +nest g controller cluster app/project --dry-run +nest g service cluster app/project --dry-run +nest g class project.entity app/project --no-spec --dry-run +nest g controller project app --dry-run +nest g service project app --dry-run + # scaffold notifications module nest g module app/notifications --dry-run nest g controller notification app/notifications --dry-run diff --git a/PLAYBOOK.md b/PLAYBOOK.md index d3b7e6ca5..3eaabcebc 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -10,11 +10,11 @@ Do-it-yourself step-by-step instructions to create this project structure from s | -------------------- | -------- | -------- | | Node | v11.10.0 | | | NPM | v6.8.0 | | -| Angular CLI | v7.3.0 | | -| @nrwl/schematics | v7.5.0 | | -| @nestjs/cli | v5.7.1 | | +| Angular CLI | v7.3.2 | | +| @nrwl/schematics | v7.6.2 | | +| @nestjs/cli | v5.8.0 | | | semantic-release-cli | v4.1.0 | | -| commitizen | v3.0.5 | | +| commitizen | v3.0.6 | | ### Install Prerequisites @@ -81,7 +81,7 @@ ng config -g schematics.@nrwl/schematics:library.unitTestRunner jest ng config -g schematics@ngx-formly/schematics:component.styleext scss # check your global defaults more cat ~/.angular-config.json -# show dependency tree for specified package. +# find reverse dependencies for a package npm ls jasmine-marbles ``` @@ -255,18 +255,24 @@ ng g lib experiments --routing --lazy --prefix=ngx --parent-module=libs/dashb ng g lib widgets --routing --lazy --prefix=ngx --parent-module=libs/dashboard/src/lib/dashboard.module.ts --unit-test-runner=jest --tags=child-module ng g lib grid --routing --lazy --prefix=ngx --parent-module=libs/dashboard/src/lib/dashboard.module.ts --unit-test-runner=jest --tags=child-module -ng g lib animations --module false -tags=utils --unit-test-runner=jest -d -ng g lib Tree --module false --publishable=true --tags=utils --unit-test-runner=jest -d -ng g lib utils --module false --publishable=true --tags=utils --unit-test-runner=jest -d +ng g lib animations --framework=none -tags=utils --unit-test-runner=jest -d +ng g lib Tree --framework=none --publishable=true --tags=utils --unit-test-runner=jest -d +ng g lib utils --framework=none --publishable=true --tags=utils --unit-test-runner=jest -d # system wide `models` module -ng g lib models --module false --tags=utils --unit-test-runner=jest -d +ng g lib models --framework none --tags=utils --unit-test-runner=jest -d ng g interface User --project=models --type=model -d ng g interface Profile --project=models --type=model -d ng g interface Image --project=models --type=model -d -ng g enum ImageType --project=models --type=enum -d -ng g enum Gender --project=models --type=enum -d -ng g enum AccountSourceType --project=models --type=enum -d - +ng g enum ImageType --project=models -d +ng g enum Gender --project=models -d +ng g enum AccountSourceType --project=models -d +ng g enum ZoneType --project=models -d +ng g enum EnvironmentType --project=models -d +ng g interface Labels --project=models --type=model -d +ng g interface Membership --project=models --type=model -d +ng g interface ResourceQuota --project=models --type=model -d +ng g interface Project --project=models --type=model -d +ng g interface Cluster --project=models --type=model -d # add `core` module which will be only inported into root/app module. ng g lib core --tags=core-module --unit-test-runner=jest -d @@ -384,7 +390,7 @@ ng g component components/totalCounter --project=clap -s -t --skip-tests --fla ng g component components/fab --project=clap -s -t --skip-tests --flat -d # generate components for `ngx-utils` Module -ng g lib ngxUtils --tags=public-module,utils --module false --publishable=true --unit-test-runner=jest +ng g lib ngxUtils --tags=public-module,utils --framework=none --publishable=true --unit-test-runner=jest ng g module pipes/truncate --project=ngx-utils --skip-tests -d ng g pipe pipes/truncate/Characters --project=ngx-utils --module=truncate --export -d ng g pipe pipes/truncate/Words --project=ngx-utils --module=truncate --export -d diff --git a/README.md b/README.md index 76cebc400..165c77747 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ [![Build Status](https://travis-ci.org/xmlking/ngx-starter-kit.svg?branch=master)](https://travis-ci.org/xmlking/ngx-starter-kit) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) +[![Greenkeeper badge](https://badges.greenkeeper.io/xmlking/ngx-starter-kit.svg)](https://greenkeeper.io/) -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) using [Nrwl Nx](https://nrwl.io/nx). +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) using [Nrwl Nx](https://nx.dev/). live [Demo](https://xmlking.github.io/ngx-starter-kit/index.html) diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index f78bee242..6d53a10f7 100755 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -9,6 +9,7 @@ import { AppController } from './app.controller'; import { NotificationsModule } from './notifications'; import { ExternalModule } from './external'; import { CacheModule } from './cache'; +import { ProjectModule } from './project'; @Module({ imports: [ @@ -19,6 +20,7 @@ import { CacheModule } from './cache'; { path: '/auth', module: AuthModule }, { path: '/user', module: UserModule }, // { path: '/account', module: AccountModule }, + { path: '/', module: ProjectModule }, { path: '/', module: NotificationsModule }, ], }, @@ -32,6 +34,7 @@ import { CacheModule } from './cache'; // ChatModule, ExternalModule, NotificationsModule, + ProjectModule, ], controllers: [AppController], }) diff --git a/apps/api/src/app/auth/auth.service.ts b/apps/api/src/app/auth/auth.service.ts index 0fe604434..26b84d620 100644 --- a/apps/api/src/app/auth/auth.service.ts +++ b/apps/api/src/app/auth/auth.service.ts @@ -14,7 +14,7 @@ export class AuthService extends CrudService { async getLoggedUserOrCreate(token: JwtToken): Promise { const { email, preferred_username } = token; // const user = await this.userRepository.findOne({email}); - const user = await this.userRepository.findOne({ userId: preferred_username }); + const user = await this.userRepository.findOne({ username: preferred_username }); if (user) { return user; } else { @@ -22,7 +22,7 @@ export class AuthService extends CrudService { firstName: token.given_name, lastName: token.family_name, email: token.email, - userId: token.preferred_username, + username: token.preferred_username, }; return super.create(newUser); } diff --git a/apps/api/src/app/auth/dto/create-user.dto.ts b/apps/api/src/app/auth/dto/create-user.dto.ts index 031fcb9dc..f6e17d811 100644 --- a/apps/api/src/app/auth/dto/create-user.dto.ts +++ b/apps/api/src/app/auth/dto/create-user.dto.ts @@ -19,10 +19,9 @@ export class CreateUserDto { @ApiModelProperty({ type: String, minLength: 8, maxLength: 20 }) @IsAscii() - @IsNotEmpty() @MinLength(8) @MaxLength(20) @IsString() @IsNotEmpty() - readonly userId: string; + readonly username: string; } diff --git a/apps/api/src/app/auth/guards/allow.guard.ts b/apps/api/src/app/auth/guards/allow.guard.ts index f0210d472..31cee9eb0 100644 --- a/apps/api/src/app/auth/guards/allow.guard.ts +++ b/apps/api/src/app/auth/guards/allow.guard.ts @@ -8,9 +8,9 @@ export class AllowGuard implements CanActivate { constructor(private reflector: Reflector, private config: ConfigService) {} async canActivate(context: ExecutionContext): Promise { - const endpointAllow = this.reflector.get('allow', context.getHandler()); - // const classEndpointAllow = this.reflector.get('allow', context.getClass()); - // const endpointAllows = [...endpointAllow, ...classEndpointAllow]; + const endpointAllow = + this.reflector.get('allow', context.getHandler()) || + this.reflector.get('allow', context.getClass()); if (endpointAllow) { // skip for public diff --git a/apps/api/src/app/auth/guards/role.guard.ts b/apps/api/src/app/auth/guards/role.guard.ts index a10f1354a..306660a2b 100644 --- a/apps/api/src/app/auth/guards/role.guard.ts +++ b/apps/api/src/app/auth/guards/role.guard.ts @@ -2,14 +2,17 @@ import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, Forbi import { Reflector } from '@nestjs/core'; import { RolesEnum } from '../decorators'; -const userId = 'msId'; +const username = 'username'; @Injectable() export class RoleGuard implements CanActivate { constructor(private reflector: Reflector) {} async canActivate(context: ExecutionContext): Promise { - const endpointRoles = this.reflector.get('roles', context.getHandler()); + const methodEndpointRoles = this.reflector.get('roles', context.getHandler()) || []; + const classEndpointRoles = this.reflector.get('roles', context.getClass()) || []; + const endpointRoles = [...methodEndpointRoles, ...classEndpointRoles]; + if (!endpointRoles || endpointRoles.length === 0) { return true; } @@ -24,7 +27,7 @@ export class RoleGuard implements CanActivate { } if (endpointRoles.includes(RolesEnum.SELF)) { - if (token.preferred_username === this.resolveUserId(request)) { + if (token.preferred_username === this.resolveUsername(request)) { return true; } else { throw new ForbiddenException(`SELF use only`); @@ -58,13 +61,13 @@ export class RoleGuard implements CanActivate { return authRoles.every(val => userRoles.includes(val)); } - private resolveUserId(request: any) { + private resolveUsername(request: any) { if (request.method === 'GET' || request.method === 'DELETE') { - return request.params[userId] || request.query[userId]; + return request.params[username] || request.query[username]; } if (request.method === 'POST' || request.method === 'PATCH' || request.method === 'PUT') { - return request.params[userId] || request.body[userId]; + return request.params[username] || request.body[username]; } return null; } diff --git a/apps/api/src/app/auth/user.entity.ts b/apps/api/src/app/auth/user.entity.ts index 76ae10293..bb132d84f 100644 --- a/apps/api/src/app/auth/user.entity.ts +++ b/apps/api/src/app/auth/user.entity.ts @@ -7,13 +7,13 @@ import { RelationId, UpdateDateColumn, VersionColumn, + OneToOne, + JoinColumn, } from 'typeorm'; import { ApiModelProperty } from '@nestjs/swagger'; import { IsAscii, IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; import { Base } from '../core/entities/base.entity'; import { Image } from '../user/profile/image.entity'; -import { OneToOne } from 'typeorm/decorator/relations/OneToOne'; -import { JoinColumn } from 'typeorm/decorator/relations/JoinColumn'; import { Profile } from '../user/profile/profile.entity'; import { User as IUser } from '@ngx-starter-kit/models'; @@ -47,7 +47,7 @@ export class User extends Base implements IUser { @MaxLength(20) @Index({ unique: true }) @Column() - userId: string; + username: string; @ApiModelProperty({ type: 'string', format: 'date-time', example: '2018-11-21T06:20:32.232Z' }) @CreateDateColumn({ type: 'timestamptz' }) @@ -70,7 +70,7 @@ export class User extends Base implements IUser { @JoinColumn() profile?: Profile; - @ApiModelProperty({ type: Number }) + @ApiModelProperty({ type: Number, readOnly: true }) @RelationId((user: User) => user.profile) - profileId?: number; + readonly profileId?: number; } diff --git a/apps/api/src/app/cache/cache-config.service.ts b/apps/api/src/app/cache/cache-config.service.ts index f4e522087..ea578df17 100755 --- a/apps/api/src/app/cache/cache-config.service.ts +++ b/apps/api/src/app/cache/cache-config.service.ts @@ -8,7 +8,7 @@ export class CacheConfigService implements CacheOptionsFactory { * Example retry strategy for when redis is used for the cache * This example is only compatible with cache-manager-redis-store because it used node_redis */ - public retryStrategy() { + retryStrategy() { return { retry_strategy: (options: any) => { if (options.error && options.error.code === 'ECONNREFUSED') { @@ -25,9 +25,9 @@ export class CacheConfigService implements CacheOptionsFactory { }; } - public createCacheOptions(): CacheModuleOptions { + createCacheOptions(): CacheModuleOptions { return { - ttl: 5, // seconds + ttl: 60, // seconds max: 10, // maximum number of items in cache }; } diff --git a/apps/api/src/app/cache/cache.decorator.ts b/apps/api/src/app/cache/cache.decorator.ts new file mode 100644 index 000000000..be87baa1c --- /dev/null +++ b/apps/api/src/app/cache/cache.decorator.ts @@ -0,0 +1,52 @@ +import { CacheService } from './cache.service'; +import { CacheManagerOptions, InternalServerErrorException } from '@nestjs/common'; +import 'reflect-metadata'; +import { tap, switchMap } from 'rxjs/operators'; +import { from, Observable, of } from 'rxjs'; + +type Cacheable = (...args) => Observable; + +export function Cache(options?: CacheManagerOptions) { + return (target: any, methodName: string, descriptor: TypedPropertyDescriptor>) => { + const originalMethod = descriptor.value; + const className = target.constructor.name; + // const returnType = Reflect.getMetadata('design:returntype', target, methodName); + // if (!returnType || returnType.name !== 'Observable') { + // throw new InternalServerErrorException('Target Method should return Observable'); + // } + + // @ts-ignore + descriptor.value = function(...args: any[]) { + const cache = this.cacheService; + if (!cache || !(cache instanceof CacheService)) { + // TODO: Can we do design time check if CacheService injected? + throw new InternalServerErrorException('Target Class should inject CacheService'); + } else { + const cacheKey = `${className}:${methodName}:${args.map(a => JSON.stringify(a)).join()}`; + + return from(cache.get(cacheKey)).pipe( + switchMap(res => + res + ? of(res) + : originalMethod + .apply(this, args) + .pipe(tap((methodResult: T) => cache.set(cacheKey, methodResult, options))), + ), + ); + } + }; + + return descriptor; + }; +} + +export function CacheBuster(cacheKey: string) { + return (target: any, methodName: string, descriptor: TypedPropertyDescriptor>) => { + const originalMethod = descriptor.value; + + descriptor.value = function(...args: any[]) { + return originalMethod.apply(this, args).pipe(tap(this.cacheService.del(cacheKey))); + }; + return descriptor; + }; +} diff --git a/apps/api/src/app/cache/cache.service.spec.ts b/apps/api/src/app/cache/cache.service.spec.ts index 02f8f58d7..314e55b41 100755 --- a/apps/api/src/app/cache/cache.service.spec.ts +++ b/apps/api/src/app/cache/cache.service.spec.ts @@ -5,11 +5,15 @@ describe('CacheService', () => { let store: any = {}; - const Manager = jest.fn().mockImplementation(() => { + const Manager = jest.fn(() => { return { get: jest.fn((key: string) => store[key]), set: jest.fn((key: string, value: any, options?: { ttl: number }) => { store[key] = value; + return value; + }), + del: jest.fn(async (key: string) => { + delete store[key]; }), }; }); @@ -47,6 +51,14 @@ describe('CacheService', () => { expect(service.get('testKey')).toEqual('test value'); }); + it('should set delete value', () => { + store.testKey = 'test value'; + + expect(service.get('testKey')).toEqual('test value'); + service.del('testKey'); + expect(service.get('testKey')).toBeUndefined(); + }); + it('set should overwrite existing value', () => { store.current = 0; diff --git a/apps/api/src/app/cache/cache.service.ts b/apps/api/src/app/cache/cache.service.ts index 6c2021a5a..7a3b43a02 100755 --- a/apps/api/src/app/cache/cache.service.ts +++ b/apps/api/src/app/cache/cache.service.ts @@ -1,9 +1,10 @@ -import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; +import { CACHE_MANAGER, CacheManagerOptions, Inject, Injectable } from '@nestjs/common'; export interface ICacheManager { - store: any; - get(key: string): any; - set(key: string, value: string, options?: { ttl: number }): any; + store?: any; + get(key: string): Promise; + set(key: string, value: T, options?: CacheManagerOptions): Promise; + del(key: string): Promise; } @Injectable() @@ -14,11 +15,15 @@ export class CacheService { this.cache = cache; } - public get(key: string): Promise { + get(key: string): Promise { return this.cache.get(key); } - public set(key: string, value: any, options?: { ttl: number }): Promise { + set(key: string, value: T, options?: CacheManagerOptions): Promise { return this.cache.set(key, value, options); } + + del(key: string): Promise { + return this.cache.del(key); + } } diff --git a/apps/api/src/app/cache/index.ts b/apps/api/src/app/cache/index.ts index f469b7329..d0b425794 100644 --- a/apps/api/src/app/cache/index.ts +++ b/apps/api/src/app/cache/index.ts @@ -1,2 +1,3 @@ export * from './cache.service'; +export * from './cache.decorator'; export * from './cache.module'; diff --git a/apps/api/src/app/chat/chat.gateway.ts b/apps/api/src/app/chat/chat.gateway.ts index 3c6ef063b..5050c19a4 100644 --- a/apps/api/src/app/chat/chat.gateway.ts +++ b/apps/api/src/app/chat/chat.gateway.ts @@ -27,9 +27,9 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa constructor(private chatService: ChatService) {} - public afterInit(server) {} + afterInit(server) {} - public handleConnection(client) { + handleConnection(client) { const event = 'connected'; client.emit(event, { message: 'Hello...' }); client.broadcast.emit(event, { @@ -37,7 +37,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa }); // this.chatService.addConnectedUser() } - public handleDisconnect(client) { + handleDisconnect(client) { const event = 'disconnected'; client.broadcast.emit(event, { message: `${client.client.conn.id} disconnected`, diff --git a/apps/api/src/app/chat/chat.service.ts b/apps/api/src/app/chat/chat.service.ts index 7b99bf418..43f124102 100644 --- a/apps/api/src/app/chat/chat.service.ts +++ b/apps/api/src/app/chat/chat.service.ts @@ -3,8 +3,8 @@ import { User } from './interfaces/user'; @Injectable() export class ChatService { - public rooms: string[] = []; - public connectedUsers: User[] = []; + rooms: string[] = []; + connectedUsers: User[] = []; addConnectedUser(user: User): void { this.connectedUsers.push(user); diff --git a/apps/api/src/app/chat/interfaces/user.ts b/apps/api/src/app/chat/interfaces/user.ts index 69b139f4f..b175cd932 100644 --- a/apps/api/src/app/chat/interfaces/user.ts +++ b/apps/api/src/app/chat/interfaces/user.ts @@ -2,5 +2,5 @@ export class User { firstName: string; lastName: string; email: string; - userId: string; + username: string; } diff --git a/apps/api/src/app/config/config.service.ts b/apps/api/src/app/config/config.service.ts index 76ac727e0..bd58919a1 100644 --- a/apps/api/src/app/config/config.service.ts +++ b/apps/api/src/app/config/config.service.ts @@ -17,18 +17,18 @@ export class ConfigService { return this.config.get(key); } - public getVersion(): string { + getVersion(): string { if (!process.env.APP_VERSION) { process.env.APP_VERSION = packageJson.version; } return process.env.APP_VERSION; } - public isProd(): boolean { + isProd(): boolean { return this.config.production; } - public getAllowWhitelist(): string[] { + getAllowWhitelist(): string[] { return this.config.ALLOW_WHITE_LIST ? this.config.ALLOW_WHITE_LIST : []; } } diff --git a/apps/api/src/app/core/context/request-context.ts b/apps/api/src/app/core/context/request-context.ts index 45d25ad1a..929e6b1dc 100644 --- a/apps/api/src/app/core/context/request-context.ts +++ b/apps/api/src/app/core/context/request-context.ts @@ -4,9 +4,9 @@ import uuid from 'uuid'; import { User } from '../../auth'; export class RequestContext { - public readonly id: number; - public request: Request; - public response: Response; + readonly id: number; + request: Request; + response: Response; constructor(request: Request, response: Response) { this.id = Math.random(); @@ -14,7 +14,7 @@ export class RequestContext { this.response = response; } - public static currentRequestContext(): RequestContext { + static currentRequestContext(): RequestContext { const session = cls.getNamespace(RequestContext.name); if (session && session.active) { return session.get(RequestContext.name); @@ -23,7 +23,7 @@ export class RequestContext { return null; } - public static currentRequest(): Request { + static currentRequest(): Request { const requestContext = RequestContext.currentRequestContext(); if (requestContext) { @@ -33,7 +33,7 @@ export class RequestContext { return null; } - public static currentUser(throwError?: boolean): User { + static currentUser(throwError?: boolean): User { const requestContext = RequestContext.currentRequestContext(); if (requestContext) { @@ -51,7 +51,7 @@ export class RequestContext { return null; } - public static currentToken(throwError?: boolean): any { + static currentToken(throwError?: boolean): any { const requestContext = RequestContext.currentRequestContext(); if (requestContext) { diff --git a/apps/api/src/app/core/crud/crud.service.ts b/apps/api/src/app/core/crud/crud.service.ts index 98209a21e..4a8119289 100644 --- a/apps/api/src/app/core/crud/crud.service.ts +++ b/apps/api/src/app/core/crud/crud.service.ts @@ -18,11 +18,7 @@ export abstract class CrudService implements ICrudService { protected constructor(protected readonly repository: Repository) {} public async getAll(options?: FindManyOptions): Promise<[T[], number]> { - const records = await this.repository.findAndCount(options); - if (records[1] === 0) { - throw new NotFoundException(`The requested records were not found`); - } - return records; + return await this.repository.findAndCount(options); } public async getOne( diff --git a/apps/api/src/app/core/entities/audit-base.entity.ts b/apps/api/src/app/core/entities/audit-base.entity.ts index 68d669489..fc8ddaca6 100644 --- a/apps/api/src/app/core/entities/audit-base.entity.ts +++ b/apps/api/src/app/core/entities/audit-base.entity.ts @@ -4,7 +4,7 @@ import { Column, CreateDateColumn, ManyToOne, - PrimaryGeneratedColumn, + PrimaryGeneratedColumn, RelationId, UpdateDateColumn, VersionColumn, } from 'typeorm'; @@ -57,6 +57,10 @@ export abstract class AuditBase { } } + /** + * NOTE: @BeforeUpdate won't run if you just call update(id, partialEntity) + * https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md#beforeupdate + */ @BeforeUpdate() setUpdatedByUser() { const currentUser = RequestContext.currentUser(); diff --git a/apps/api/src/app/core/index.ts b/apps/api/src/app/core/index.ts index 5b8b66ce1..0a78f2b6a 100644 --- a/apps/api/src/app/core/index.ts +++ b/apps/api/src/app/core/index.ts @@ -1,4 +1,5 @@ export * from './core.module'; export * from './services/base-remote.service'; export * from './crud/crud.service'; +export * from './context/request-context'; export * from './crud/crud.controller'; diff --git a/apps/api/src/app/email/email.module.ts b/apps/api/src/app/email/email.module.ts index 1a46ad163..0d5a80d5d 100644 --- a/apps/api/src/app/email/email.module.ts +++ b/apps/api/src/app/email/email.module.ts @@ -1,7 +1,6 @@ import { Module, DynamicModule } from '@nestjs/common'; import { EmailCoreModule } from './email-core.module'; import { EmailModuleOptions } from './interfaces/email-options.interface'; -import { EmailService } from './email.service'; @Module({}) export class EmailModule { diff --git a/apps/api/src/app/email/email.service.ts b/apps/api/src/app/email/email.service.ts index 3b4bc66b9..c35b8e526 100644 --- a/apps/api/src/app/email/email.service.ts +++ b/apps/api/src/app/email/email.service.ts @@ -28,7 +28,7 @@ export class EmailService { this.transporter.use('compile', this.renderTemplate(templateDir)); } - public async sendMail(sendMailOptions: SendMailOptions & EmailTemplate): Promise { + async sendMail(sendMailOptions: SendMailOptions & EmailTemplate): Promise { return await this.transporter.sendMail(sendMailOptions); } diff --git a/apps/api/src/app/external/external.module.ts b/apps/api/src/app/external/external.module.ts index 8e88e4fdc..ca35bcac4 100644 --- a/apps/api/src/app/external/external.module.ts +++ b/apps/api/src/app/external/external.module.ts @@ -1,11 +1,11 @@ import { HttpModule, Module } from '@nestjs/common'; import { SharedModule } from '../shared'; -import { KubernetesService } from './kubernetes/kubernetes.service'; -import { KubernetesController } from './kubernetes/kubernetes.controller'; +import { WeatherController } from './weather/weather.controller'; +import { WeatherService } from './weather/weather.service'; @Module({ imports: [SharedModule, HttpModule.register({ timeout: 5000 })], - providers: [KubernetesService], - controllers: [KubernetesController], + providers: [WeatherService], + controllers: [WeatherController], }) export class ExternalModule {} diff --git a/apps/api/src/app/external/weather/weather.controller.spec.ts b/apps/api/src/app/external/weather/weather.controller.spec.ts new file mode 100644 index 000000000..8e2cc46cd --- /dev/null +++ b/apps/api/src/app/external/weather/weather.controller.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { WeatherController } from './weather.controller'; + +describe('Weather Controller', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + controllers: [WeatherController], + }).compile(); + }); + it('should be defined', () => { + const controller: WeatherController = module.get(WeatherController); + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/external/weather/weather.controller.ts b/apps/api/src/app/external/weather/weather.controller.ts new file mode 100644 index 000000000..251195972 --- /dev/null +++ b/apps/api/src/app/external/weather/weather.controller.ts @@ -0,0 +1,31 @@ +import { CacheInterceptor, Controller, Get, HttpStatus, Logger, Param, UseInterceptors } from '@nestjs/common'; +import { ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTags, ApiExcludeEndpoint } from '@nestjs/swagger'; +import { WeatherService } from './weather.service'; +import { Observable } from 'rxjs'; + +@ApiOAuth2Auth(['read']) +@ApiUseTags('External', 'Weather') +@ApiResponse({ status: HttpStatus.OK, description: 'Found' }) +@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Not Found' }) +@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' }) +@ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden' }) +@Controller('weather') +export class WeatherController { + private readonly logger = new Logger(WeatherController.name); + constructor(private readonly weatherService: WeatherService) {} + + @ApiOperation({ title: 'get weather by zipCode e.g., 91501' }) + @UseInterceptors(CacheInterceptor) + @Get(':zip') + getWeather(@Param('zip') zip: string): Observable { + this.logger.log('weather zip', zip); + return this.weatherService.getWeatherByZip(zip); + } + + @ApiOperation({ title: 'get forecast by zipCode e.g., 91501' }) + @Get('forecast/:zip') + getForecast(@Param('zip') zip: string): Observable { + this.logger.log('forecast zip', zip); + return this.weatherService.getForecastByZip(zip); + } +} diff --git a/apps/api/src/app/external/weather/weather.service.spec.ts b/apps/api/src/app/external/weather/weather.service.spec.ts new file mode 100644 index 000000000..72e153763 --- /dev/null +++ b/apps/api/src/app/external/weather/weather.service.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { WeatherService } from './weather.service'; + +describe('WeatherService', () => { + let service: WeatherService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [WeatherService], + }).compile(); + service = module.get(WeatherService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/external/weather/weather.service.ts b/apps/api/src/app/external/weather/weather.service.ts new file mode 100644 index 000000000..c3914c480 --- /dev/null +++ b/apps/api/src/app/external/weather/weather.service.ts @@ -0,0 +1,66 @@ +import { + BadRequestException, + HttpException, + HttpService, + HttpStatus, + Injectable, + Logger, + NotFoundException, +} from '@nestjs/common'; +import { AxiosError, AxiosResponse } from 'axios'; +import { Observable, throwError } from 'rxjs'; +import { catchError, map, tap } from 'rxjs/operators'; +import { environment as env } from '@env-api/environment'; + +import { BaseRemoteService } from '../../core'; +import { CacheService, Cache } from '../../cache'; + +@Injectable() +export class WeatherService extends BaseRemoteService { + readonly logger = new Logger(WeatherService.name); + readonly baseUrl = env.weather.baseUrl; + readonly timeout = 10000; + + constructor(http: HttpService, private cacheService: CacheService) { + super(http); + } + + getWeatherByZip(zip: string, countryCode: string = 'us'): Observable { + return this.http.get(`${this.baseUrl}/weather?zip=${zip},${countryCode}&appid=${env.weather.apiKey}`, this.getHeaders()).pipe( + tap(res => this.logger.log(`Status: ${res.status}`)), + map(res => res.data ), + catchError(this.handleError), + ); + } + + // 5 day / 3 hour forecast data + @Cache({ ttl: 60 * 60 * 3}) + getForecastByZip(zip: string, countryCode: string = 'us'): Observable { + return this.http.get(`${this.baseUrl}/forecast?zip=${zip},${countryCode}&appid=${env.weather.apiKey}`, this.getHeaders()).pipe( + tap(res => this.logger.log(`Status: ${res.status}`)), + map(res => res.data ), + catchError(this.handleError), + ); + } + + getHeaders() { + return { + headers: { Authorization: `Basic ${env.weather.apiKey}` }, + timeout: this.timeout, + }; + } + + handleError(error: AxiosError) { + if (error.response) { + if (error.response.status === HttpStatus.NOT_FOUND) { + return throwError(new NotFoundException(error.response.data)); + } else if (error.response.status === HttpStatus.BAD_REQUEST) { + return throwError(new BadRequestException(error.response.data)); + } else { + return throwError(new HttpException(error.response.data, error.response.status)); + } + } else { + return throwError(error.message); + } + } +} diff --git a/apps/api/src/app/notifications/notification/notification.controller.ts b/apps/api/src/app/notifications/notification/notification.controller.ts index 65f90142e..d59b587b6 100644 --- a/apps/api/src/app/notifications/notification/notification.controller.ts +++ b/apps/api/src/app/notifications/notification/notification.controller.ts @@ -18,7 +18,7 @@ export class NotificationController extends CrudController { @ApiOperation({ title: 'find all Notifications. Admins only' }) @ApiResponse({ status: HttpStatus.OK, description: 'All Notifications', /* type: Notification, */ isArray: true }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get() async findAll(): Promise<[Notification[], number]> { @@ -39,7 +39,7 @@ export class NotificationController extends CrudController { @ApiOperation({ title: 'Find by id. Admins only' }) @ApiResponse({ status: HttpStatus.OK, description: 'Found one record', type: Notification }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get(':id') async findById(@Param('id') id: string): Promise { @@ -56,7 +56,7 @@ export class NotificationController extends CrudController { status: HttpStatus.BAD_REQUEST, description: 'Invalid input, The response body may contain clues as to what went wrong', }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Post() async create(@Body() entity: CreateNotificationDto): Promise { @@ -64,7 +64,7 @@ export class NotificationController extends CrudController { } @ApiExcludeEndpoint() - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Put(':id') async update( @@ -78,7 +78,7 @@ export class NotificationController extends CrudController { @ApiOperation({ title: 'Delete record by admin' }) @ApiResponse({ status: HttpStatus.NO_CONTENT, description: 'The record has been successfully deleted' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Delete(':id') async deleteByAdmin(@Param('id') id: string): Promise { @@ -91,7 +91,7 @@ export class NotificationController extends CrudController { // @Delete('deleteByUser/:id') // async delete(@Param('id') id: string, @CurrentUser() user: User): Promise { // return this.notificationService.update( - // { id: parseInt(id, 10), targetType: TargetType.USER, target: user.userId }, + // { id: parseInt(id, 10), targetType: TargetType.USER, target: user.username }, // { isActive: false }, // ); // } @@ -105,7 +105,7 @@ export class NotificationController extends CrudController { status: HttpStatus.BAD_REQUEST, description: 'Invalid input, The response body may contain clues as to what went wrong', }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Post('send') async send(@Body() notif: SendNotificationDto) { diff --git a/apps/api/src/app/notifications/notification/notification.service.ts b/apps/api/src/app/notifications/notification/notification.service.ts index 4ac67da3d..abf1a102d 100644 --- a/apps/api/src/app/notifications/notification/notification.service.ts +++ b/apps/api/src/app/notifications/notification/notification.service.ts @@ -30,8 +30,8 @@ export class NotificationService extends CrudService implements On this.eventBus.off(DeleteNotification.type, this.onDeleteNotification.bind(this)); } - public async getUserNotifications(user: User): Promise<[Notification[], number]> { - const records = await this.repository.findAndCount({ target: In(['all', user.userId]), isActive: true }); + async getUserNotifications(user: User): Promise<[Notification[], number]> { + const records = await this.repository.findAndCount({ target: In(['all', user.username]), isActive: true }); if (records[1] === 0) { throw new NotFoundException(`The requested records were not found`); } @@ -53,7 +53,7 @@ export class NotificationService extends CrudService implements On let count: number; switch (notification.targetType) { case TargetType.USER: - [subscriptions, count] = await this.subscriptionService.findAndCount({ userId: notification.target }); + [subscriptions, count] = await this.subscriptionService.findAndCount({ username: notification.target }); break; case TargetType.TOPIC: // FIXME: https://github.com/typeorm/typeorm/issues/3150 @@ -72,13 +72,13 @@ export class NotificationService extends CrudService implements On async onMarkAsRead(action: MarkAsRead, user: User) { await this.update( - { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.userId }, + { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.username }, { read: true }, ); } async onDeleteNotification(action: DeleteNotification, user: User) { await this.update( - { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.userId }, + { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.username }, { isActive: false }, ); } diff --git a/apps/api/src/app/notifications/subscription/subscription.controller.ts b/apps/api/src/app/notifications/subscription/subscription.controller.ts index 8749601d9..3688854ab 100644 --- a/apps/api/src/app/notifications/subscription/subscription.controller.ts +++ b/apps/api/src/app/notifications/subscription/subscription.controller.ts @@ -18,7 +18,7 @@ export class SubscriptionController extends CrudController { @ApiOperation({ title: 'find all Subscriptions. Admins only' }) @ApiResponse({ status: HttpStatus.OK, description: 'All Subscriptions', /* type: Subscription, */ isArray: true }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get() async findAll(): Promise<[Subscription[], number]> { @@ -39,7 +39,7 @@ export class SubscriptionController extends CrudController { @ApiOperation({ title: 'Find by id. Admins only' }) @ApiResponse({ status: HttpStatus.OK, description: 'Found one record', type: Subscription }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get(':id') async findById(@Param('id') id: string): Promise { @@ -60,7 +60,7 @@ export class SubscriptionController extends CrudController { async create(@Body() entity: CreateSubscriptionDto, @CurrentUser() user: User): Promise { const records = await this.subscriptionService.findAndCount({ endpoint: entity.endpoint }); if (records[1] === 0) { - return super.create({ ...entity, userId: user.userId }); + return super.create({ ...entity, username: user.username }); } else { return records[0][0]; } @@ -80,9 +80,9 @@ export class SubscriptionController extends CrudController { @CurrentUser() user: User, ): Promise { if (id.startsWith('http')) { - return this.subscriptionService.update({ endpoint: id, userId: user.userId }, entity); + return this.subscriptionService.update({ endpoint: id, username: user.username }, entity); } else { - return this.subscriptionService.update({ id: parseInt(id, 10), userId: user.userId }, entity); + return this.subscriptionService.update({ id: parseInt(id, 10), username: user.username }, entity); } } @@ -92,9 +92,9 @@ export class SubscriptionController extends CrudController { @Delete(':id') async delete(@Param('id') id: string, @CurrentUser() user: User): Promise { if (id.startsWith('http')) { - return this.subscriptionService.delete({ endpoint: id, userId: user.userId }); + return this.subscriptionService.delete({ endpoint: id, username: user.username }); } else { - return this.subscriptionService.delete({ id: parseInt(id, 10), userId: user.userId }); + return this.subscriptionService.delete({ id: parseInt(id, 10), username: user.username }); } } } diff --git a/apps/api/src/app/notifications/subscription/subscription.entity.ts b/apps/api/src/app/notifications/subscription/subscription.entity.ts index 74d2ba3e9..d3c38b818 100644 --- a/apps/api/src/app/notifications/subscription/subscription.entity.ts +++ b/apps/api/src/app/notifications/subscription/subscription.entity.ts @@ -21,7 +21,7 @@ export class Subscription extends Base { @ApiModelProperty({ type: String, minLength: 3, maxLength: 20 }) @Index() @Column() - userId: string; + username: string; @ApiModelProperty({ type: String, isArray: true }) @Column('text', { nullable: true, array: true }) diff --git a/apps/api/src/app/notifications/subscription/subscription.service.ts b/apps/api/src/app/notifications/subscription/subscription.service.ts index e5b097558..7cabe0905 100644 --- a/apps/api/src/app/notifications/subscription/subscription.service.ts +++ b/apps/api/src/app/notifications/subscription/subscription.service.ts @@ -24,8 +24,8 @@ export class SubscriptionService extends CrudService { return this.subscriptionRepository.find(conditions); } - public async getUserSubscriptions(user: User): Promise<[Subscription[], number]> { - const records = await this.repository.findAndCount({ userId: user.userId }); + async getUserSubscriptions(user: User): Promise<[Subscription[], number]> { + const records = await this.repository.findAndCount({ username: user.username }); if (records[1] === 0) { throw new NotFoundException(`The requested records were not found`); } diff --git a/apps/api/src/app/project/README.md b/apps/api/src/app/project/README.md new file mode 100644 index 000000000..c85756762 --- /dev/null +++ b/apps/api/src/app/project/README.md @@ -0,0 +1,33 @@ +# Projects + +API to manage multiple Kubernetes clusters. + +A **Project** represent a *Cluster, Namespace, User Account, Resource Quotas, Billing* etc. + + +### Setup +Setup your [Docker for Mac](https://gist.github.com/xmlking/62ab53753c0f0f5247d0e174b31dab21) for local development. +Create a cluster via [Swagger API](http://localhost:3000/docs/#/Cluster/post_api_cluster) +> `YOUR_KUBERNETES_TOKEN` can be retrieved from kubernetes dashboard + +```json +{ + "name": "LOC", + "ver": "1.13", + "baseUrl": "https://localhost:6443", + "token": "YOUR_KUBERNETES_TOKEN" +} +``` + +### Test +```bash +# Get all namespaces in a clusterName +curl -X GET "http://localhost:3000/api/kubernetes/LOC" \ +-H "accept: application/json" \ +-H "authorization: Bearer YOUR_OIDC_ACCESS_TOKEN" + +# Find one namespace in a cluster by namespaceName +curl -X GET "http://localhost:3000/api/kubernetes/LOC/kube-system" \ +-H "accept: application/json" \ +-H "authorization: Bearer YOUR_OIDC_ACCESS_TOKEN" +``` diff --git a/apps/api/src/app/project/cluster/cluster.controller.spec.ts b/apps/api/src/app/project/cluster/cluster.controller.spec.ts new file mode 100644 index 000000000..8218b1c35 --- /dev/null +++ b/apps/api/src/app/project/cluster/cluster.controller.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ClusterController } from './cluster.controller'; + +describe('Cluster Controller', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + controllers: [ClusterController], + }).compile(); + }); + it('should be defined', () => { + const controller: ClusterController = module.get(ClusterController); + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/cluster/cluster.controller.ts b/apps/api/src/app/project/cluster/cluster.controller.ts new file mode 100644 index 000000000..a4d95da79 --- /dev/null +++ b/apps/api/src/app/project/cluster/cluster.controller.ts @@ -0,0 +1,67 @@ +import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTags } from '@nestjs/swagger'; +import { CrudController } from '../../core'; +import { Cluster } from './cluster.entity'; +import { ClusterService } from './cluster.service'; +import { Roles, RolesEnum } from '../../auth'; +import { CreateClusterDto } from './dto/create-cluster.dto'; +import { UpdateClusterDto } from './dto/update-cluster.dto'; +import { Observable } from 'rxjs'; + +@ApiOAuth2Auth(['read']) +@ApiUseTags('Project', 'Cluster', 'Admin') +@Roles(RolesEnum.ADMIN) +@Controller('cluster') +export class ClusterController extends CrudController { + constructor(private readonly clusterService: ClusterService) { + super(clusterService); + } + + @Roles(RolesEnum.USER) + @ApiOperation({ title: 'Get kubernetes cluster names' }) + @ApiResponse({ status: HttpStatus.OK, description: 'All records', type: String, isArray: true }) + @Get('clusterNames') + getClusterNames(): Observable { + return this.clusterService.getClusterNames(); + } + + @ApiOperation({ title: 'Find by Name' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Found one record', type: Cluster }) + @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) + @Get('byName/:name') + async findByName(@Param('name') name: string): Promise { + return this.clusterService.getOne({ name }); + } + + @ApiOperation({ title: 'Create new record' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'The record has been successfully created.', type: Cluster }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input, The response body may contain clues as to what went wrong', + }) + @Post() + async create(@Body() entity: CreateClusterDto): Promise { + return super.create(entity); + } + + @ApiOperation({ title: 'Update an existing record' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'The record has been successfully edited.' }) + @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input, The response body may contain clues as to what went wrong', + }) + @Put(':id') + async update(@Param('id') id: string, @Body() entity: UpdateClusterDto): Promise { + return super.update(id, entity); + } + + // @Roles(RolesEnum.ADMIN) // FIXME: remove me: class level not working. + // @ApiOperation({ title: 'Delete record' }) + // @ApiResponse({ status: HttpStatus.NO_CONTENT, description: 'The record has been successfully deleted' }) + // @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) + // @Delete(':id') + // async delete(@Param('id') id: string, ...options: any[]): Promise { + // return super.delete(id); + // } +} diff --git a/apps/api/src/app/project/cluster/cluster.entity.ts b/apps/api/src/app/project/cluster/cluster.entity.ts new file mode 100644 index 000000000..70e99bcd6 --- /dev/null +++ b/apps/api/src/app/project/cluster/cluster.entity.ts @@ -0,0 +1,47 @@ +import { Column, Entity, Index, OneToMany, RelationId } from 'typeorm'; +import { ApiModelProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; +import { IsAscii, IsNotEmpty, IsUrl } from 'class-validator'; +import { AuditBase } from '../../core/entities/audit-base.entity'; +import { Project } from '../project.entity'; +import { Cluster as ICluster } from '@ngx-starter-kit/models'; + +@Entity('cluster') +export class Cluster extends AuditBase implements ICluster { + + @ApiModelProperty({ type: String, minLength: 3, maxLength: 15 }) + @IsNotEmpty() + @IsAscii() + @Index({ unique: true }) + @Column({length: 15 }) + name: string; + + @ApiModelProperty({ type: String, minLength: 3, maxLength: 6 }) + @IsNotEmpty() + @Column({length: 6 }) + ver: string; + + @ApiModelProperty({ type: String, minLength: 10, maxLength: 256 }) + @IsNotEmpty() + @IsUrl() + @Column() + baseUrl: string; + + @ApiModelProperty({ type: String, minLength: 256 }) + @Exclude() + @IsNotEmpty() + @Column() + token: string; + + @ApiModelProperty({ type: Project, isArray: true }) + @OneToMany(_ => Project, project => project.cluster) + projects?: Project[]; + + @ApiModelProperty({ type: Number, readOnly: true }) + @RelationId((cluster: Cluster) => cluster.createdBy) + readonly createdById?: number; + + @ApiModelProperty({ type: Number, readOnly: true }) + @RelationId((cluster: Cluster) => cluster.updatedBy) + readonly updatedById?: number; +} diff --git a/apps/api/src/app/project/cluster/cluster.service.spec.ts b/apps/api/src/app/project/cluster/cluster.service.spec.ts new file mode 100644 index 000000000..690626fcb --- /dev/null +++ b/apps/api/src/app/project/cluster/cluster.service.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ClusterService } from './cluster.service'; + +describe('ClusterService', () => { + let service: ClusterService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ClusterService], + }).compile(); + service = module.get(ClusterService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/cluster/cluster.service.ts b/apps/api/src/app/project/cluster/cluster.service.ts new file mode 100644 index 000000000..b803732a8 --- /dev/null +++ b/apps/api/src/app/project/cluster/cluster.service.ts @@ -0,0 +1,43 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FindConditions, Repository, UpdateResult } from 'typeorm'; +import { CrudService, RequestContext } from '../../core'; +import { Cluster } from './cluster.entity'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { Cache, CacheService } from '../../cache'; +import { from } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class ClusterService extends CrudService { + constructor( + @InjectRepository(Cluster) private readonly clusterRepository: Repository, + private cacheService: CacheService, + ) { + super(clusterRepository); + } + + @Cache({ ttl: 60 * 60 * 3 }) + getClusterNames() { + return from(super.getAll()).pipe(map(([clusters, size]) => clusters.map(c => c.name))); + } + + /** + * FIXME: workaround as @BeforeUpdate don't work with repository.update(). + */ + async update( + id: string | number | FindConditions, + partialEntity: QueryDeepPartialEntity, + ...options: any[] + ): Promise { + try { + const user = RequestContext.currentUser(); + if (user) { + partialEntity.updatedBy = user; + } + return await this.repository.update(id, partialEntity); + } catch (err /*: WriteError*/) { + throw new BadRequestException(err); + } + } +} diff --git a/apps/api/src/app/project/cluster/dto/create-cluster.dto.ts b/apps/api/src/app/project/cluster/dto/create-cluster.dto.ts new file mode 100644 index 000000000..36fcb3dac --- /dev/null +++ b/apps/api/src/app/project/cluster/dto/create-cluster.dto.ts @@ -0,0 +1,30 @@ +import { IsAscii, IsNotEmpty, IsString, IsUrl, MaxLength, MinLength } from 'class-validator'; +import { ApiModelProperty } from '@nestjs/swagger'; + +export class CreateClusterDto { + @ApiModelProperty({ type: String, minLength: 3, maxLength: 15 }) + @IsNotEmpty() + @IsAscii() + @MinLength(3) + @MaxLength(15) + name: string; + + @ApiModelProperty({ type: String, minLength: 3, maxLength: 6 }) + @IsNotEmpty() + @MinLength(3) + @MaxLength(6) + ver: string; + + @ApiModelProperty({ type: String, minLength: 10, maxLength: 256 }) + @IsNotEmpty() + @IsUrl() + @MinLength(10) + @MaxLength(256) + baseUrl: string; + + @ApiModelProperty({ type: String, minLength: 256 }) + @IsNotEmpty() + @IsString() + @MinLength(256) + token: string; +} diff --git a/apps/api/src/app/project/cluster/dto/update-cluster.dto.ts b/apps/api/src/app/project/cluster/dto/update-cluster.dto.ts new file mode 100644 index 000000000..e8754b6e3 --- /dev/null +++ b/apps/api/src/app/project/cluster/dto/update-cluster.dto.ts @@ -0,0 +1,23 @@ +import { IsOptional, IsString, IsUrl, MaxLength, MinLength } from 'class-validator'; +import { ApiModelPropertyOptional } from '@nestjs/swagger'; + +export class UpdateClusterDto { + @ApiModelPropertyOptional({ type: String, minLength: 3, maxLength: 6 }) + @IsOptional() + @MinLength(3) + @MaxLength(6) + ver?: string; + + @ApiModelPropertyOptional({ type: String, minLength: 10, maxLength: 256 }) + @IsOptional() + @IsUrl() + @MinLength(10) + @MaxLength(256) + baseUrl?: string; + + @ApiModelPropertyOptional({ type: String, minLength: 256 }) + @IsOptional() + @IsString() + @MinLength(256) + token?: string; +} diff --git a/apps/api/src/app/project/dto/create-project.dto.ts b/apps/api/src/app/project/dto/create-project.dto.ts new file mode 100644 index 000000000..332d0b786 --- /dev/null +++ b/apps/api/src/app/project/dto/create-project.dto.ts @@ -0,0 +1,70 @@ +import { ArrayUnique, IsAscii, IsEmail, IsNotEmpty, IsOptional, IsString, Length, Matches, ValidateNested } from 'class-validator'; +import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; +import { Labels } from './labels.dto'; +import { Type } from 'class-transformer'; +import { ResourceQuota } from './resource-quota'; + +export class CreateProjectDto { + @ApiModelProperty({ type: String }) + @IsString() + @Matches(/^[a-zA-Z0-9\d-]+$/) + readonly name: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + readonly description?: string; + + @ApiModelProperty({ type: String }) + @IsString() + readonly clusterName: string; + + @ApiModelProperty({ type: String, minLength: 5, maxLength: 100 }) + @IsAscii() + @IsNotEmpty() + @Length(5, 100) + @Matches(/^[a-z\d-]+$/) + readonly namespace: string; + + @ApiModelProperty({ type: String, minLength: 5, maxLength: 100 }) + @IsAscii() + @IsNotEmpty() + @Length(5, 100) + @Matches(/^[a-z\d-]+$/) + readonly serviceAccountName: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + username?: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + @IsEmail() + email?: string; + + @ApiModelProperty({ type: String, minLength: 5, maxLength: 100 }) + @IsNotEmpty() + @IsString() + @Length(5, 100) + readonly billingId: string; + + @ApiModelProperty({ type: Labels }) + @IsNotEmpty() + @ValidateNested() + @Type(() => Labels) + readonly labels: Labels; + + @ApiModelPropertyOptional({ type: String, isArray: true }) + @IsOptional() + @IsString({ each: true }) + @ArrayUnique() + readonly tags?: Set; + + @ApiModelPropertyOptional({ type: ResourceQuota }) + @IsOptional() + @ValidateNested() + @Type(() => ResourceQuota) + resourceQuota: ResourceQuota; +} diff --git a/apps/api/src/app/project/dto/labels.dto.ts b/apps/api/src/app/project/dto/labels.dto.ts new file mode 100644 index 000000000..7e8da381e --- /dev/null +++ b/apps/api/src/app/project/dto/labels.dto.ts @@ -0,0 +1,17 @@ +import { IsEnum, IsString } from 'class-validator'; +import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; +import { EnvironmentType, Labels as ILabels, ZoneType } from '@ngx-starter-kit/models'; + +export class Labels implements ILabels { + @ApiModelProperty({ type: String, enum: EnvironmentType, default: EnvironmentType.NonProd }) + @IsEnum(EnvironmentType) + env: EnvironmentType; + + @ApiModelProperty({ type: String, enum: ZoneType, default: ZoneType.Core }) + @IsEnum(ZoneType) + zone: ZoneType; + + @ApiModelPropertyOptional({ type: String }) + @IsString() + name?: string; +} diff --git a/apps/api/src/app/project/dto/limits.ts b/apps/api/src/app/project/dto/limits.ts new file mode 100644 index 000000000..78302fea7 --- /dev/null +++ b/apps/api/src/app/project/dto/limits.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsNumber, Max, Min } from 'class-validator'; +import { ApiModelProperty } from '@nestjs/swagger'; +import { Limits as ILimits } from '@ngx-starter-kit/models'; + +export class Limits implements ILimits { + @ApiModelProperty({ type: Number, minimum: 0, maximum: 10156, default: 1 }) + @IsNumber() + @IsNotEmpty() + @Min(0) + @Max(10156) + cpu: number; + + @ApiModelProperty({ type: Number, minimum: 0, maximum: 10720, default: 1 }) + @IsNumber() + @IsNotEmpty() + @Min(0) + @Max(10720) + memory: number; +} diff --git a/apps/api/src/app/project/dto/membership.ts b/apps/api/src/app/project/dto/membership.ts new file mode 100644 index 000000000..256ebe6c3 --- /dev/null +++ b/apps/api/src/app/project/dto/membership.ts @@ -0,0 +1,22 @@ +import { ArrayNotEmpty, ArrayUnique, IsArray, IsEnum, IsNotEmpty, IsString } from 'class-validator'; +import { ApiModelProperty } from '@nestjs/swagger'; +import { Membership as IMembership, MembershipType, RoleType } from '@ngx-starter-kit/models'; + +export class Membership implements IMembership { + @ApiModelProperty({ type: String }) + @IsString() + @IsNotEmpty() + // @Matches(/^NGX_K8S_/) // TODO: if type===group then check pattern + name: string; + + @ApiModelProperty({ type: String, enum: MembershipType }) + @IsEnum(MembershipType) + type: MembershipType; + + @ApiModelProperty({ type: RoleType, enum: RoleType, isArray: true }) + @IsArray() + @ArrayNotEmpty() + @ArrayUnique() + @IsEnum(RoleType, { each: true }) + roles: Set; +} diff --git a/apps/api/src/app/project/dto/requests.ts b/apps/api/src/app/project/dto/requests.ts new file mode 100644 index 000000000..8025884e0 --- /dev/null +++ b/apps/api/src/app/project/dto/requests.ts @@ -0,0 +1,26 @@ +import { IsNotEmpty, IsNumber, IsOptional, Max, Min } from 'class-validator'; +import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; +import { Requests as IRequests } from '@ngx-starter-kit/models'; + +export class Requests implements IRequests { + @ApiModelProperty({ type: Number, minimum: 0, maximum: 10156, default: 1 }) + @IsNumber() + @IsNotEmpty() + @Min(0) + @Max(10156) + cpu: number; + + @ApiModelProperty({ type: Number, minimum: 0, maximum: 10720, default: 1 }) + @IsNumber() + @IsNotEmpty() + @Min(0) + @Max(10720) + memory: number; + + @ApiModelPropertyOptional({ type: Number, minimum: 0, maximum: 25, default: 5 }) + @IsNumber() + @IsOptional() + @Min(0) + @Max(25) + gpu: number; +} diff --git a/apps/api/src/app/project/dto/resource-quota.ts b/apps/api/src/app/project/dto/resource-quota.ts new file mode 100644 index 000000000..9c94ec599 --- /dev/null +++ b/apps/api/src/app/project/dto/resource-quota.ts @@ -0,0 +1,35 @@ +import { IsNotEmpty, IsNumber, IsOptional, Max, Min, ValidateNested } from 'class-validator'; +import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { Requests } from './requests'; +import { Limits } from './limits'; +import { ResourceQuota as IResourceQuota } from '@ngx-starter-kit/models'; + +// namespace level hard limits +export class ResourceQuota implements IResourceQuota { + @ApiModelProperty({ type: Requests }) + @IsNotEmpty() + @ValidateNested() + @Type(() => Requests) + requests: Requests; + + @ApiModelProperty({ type: Limits }) + @IsNotEmpty() + @ValidateNested() + @Type(() => Limits) + limits: Limits; + + @ApiModelPropertyOptional({ type: Number, default: 1 }) + @IsOptional() + @IsNumber() + @Min(0) + @Max(10156) + cpu?: number; + + @ApiModelPropertyOptional({ type: Number, default: 1 }) + @IsOptional() + @IsNumber() + @Min(0) + @Max(10720) + memory?: number; +} diff --git a/apps/api/src/app/project/dto/search-project.dto.ts b/apps/api/src/app/project/dto/search-project.dto.ts new file mode 100644 index 000000000..da247a6fb --- /dev/null +++ b/apps/api/src/app/project/dto/search-project.dto.ts @@ -0,0 +1,36 @@ +import { IsArray, IsAscii, IsOptional, IsString, Length, Matches, ValidateNested } from 'class-validator'; +import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; +import { Labels } from './labels.dto'; +import { Type } from 'class-transformer'; + +export class SearchProjectDto { + @ApiModelProperty({ type: [String] }) + @IsArray() + @IsOptional() + readonly groups?: string[]; + + @ApiModelProperty({ type: String }) + @IsString() + @IsOptional() + readonly username?: string; + + @ApiModelPropertyOptional({ type: String }) + @IsAscii() + @IsOptional() + @Length(5, 100) + @Matches(/^[a-z\d-]+$/) + readonly namespace?: string; + + @ApiModelPropertyOptional({ type: String, minLength: 5, maxLength: 100 }) + @IsAscii() + @IsOptional() + @Length(5, 100) + @Matches(/^[a-z\d-]+$/) + readonly serviceAccountName: string; + + @ApiModelPropertyOptional({ type: Labels }) + @IsOptional() + @ValidateNested() + @Type(() => Labels) + readonly labels?: Labels; +} diff --git a/apps/api/src/app/project/dto/update-project.dto.ts b/apps/api/src/app/project/dto/update-project.dto.ts new file mode 100644 index 000000000..165dae133 --- /dev/null +++ b/apps/api/src/app/project/dto/update-project.dto.ts @@ -0,0 +1,67 @@ +import { ArrayUnique, IsArray, IsBoolean, IsEmail, IsOptional, IsString, Length, Matches, ValidateNested } from 'class-validator'; +import { ApiModelPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { Membership } from './membership'; +import { ResourceQuota } from './resource-quota'; +import { Labels } from './labels.dto'; + +export class UpdateProjectDto { + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + @Matches(/^[a-zA-Z0-9\d-]+$/) + readonly name?: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + readonly description?: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + readonly username?: string; + + @ApiModelPropertyOptional({ type: String }) + @IsOptional() + @IsString() + @IsEmail() + readonly email?: string; + + @ApiModelPropertyOptional({ type: String, minLength: 5, maxLength: 100 }) + @IsOptional() + @IsString() + @Length(5, 100) + readonly billingId?: string; + + @ApiModelPropertyOptional({ type: Labels }) + @IsOptional() + @ValidateNested() + @Type(() => Labels) + readonly labels?: Labels; + + @ApiModelPropertyOptional({ type: String, isArray: true }) + @IsOptional() + @IsString({ each: true }) + @ArrayUnique() + readonly tags?: Set; + + @ApiModelPropertyOptional({ type: ResourceQuota }) + @IsOptional() + @ValidateNested() + @Type(() => ResourceQuota) + resourceQuota?: ResourceQuota; + + @ApiModelPropertyOptional({ type: Boolean }) + @IsOptional() + @IsBoolean() + readonly isActive?: boolean; + + @ApiModelPropertyOptional({ type: Membership, isArray: true }) + @IsOptional() + @IsArray() + @ArrayUnique() // @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => Membership) + readonly memberships?: Array; +} diff --git a/apps/api/src/app/project/index.ts b/apps/api/src/app/project/index.ts new file mode 100644 index 000000000..c3c63b704 --- /dev/null +++ b/apps/api/src/app/project/index.ts @@ -0,0 +1 @@ +export * from './project.module'; diff --git a/apps/api/src/app/project/interfaces/kube-context.ts b/apps/api/src/app/project/interfaces/kube-context.ts new file mode 100644 index 000000000..07d96eaee --- /dev/null +++ b/apps/api/src/app/project/interfaces/kube-context.ts @@ -0,0 +1,7 @@ +import { Labels } from '@ngx-starter-kit/models'; + +export interface KubeContext { + readonly cluster: string; + readonly namespace: string; + readonly labels?: Labels; +} diff --git a/apps/api/src/app/project/kubernetes/dto/.gitkeep b/apps/api/src/app/project/kubernetes/dto/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/api/src/app/project/kubernetes/kubernetes.controller.spec.ts b/apps/api/src/app/project/kubernetes/kubernetes.controller.spec.ts new file mode 100644 index 000000000..7e36a7a1b --- /dev/null +++ b/apps/api/src/app/project/kubernetes/kubernetes.controller.spec.ts @@ -0,0 +1,15 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { KubernetesController } from './kubernetes.controller'; + +describe('Kubernetes Controller', () => { + let module: TestingModule; + beforeAll(async () => { + module = await Test.createTestingModule({ + controllers: [KubernetesController], + }).compile(); + }); + it('should be defined', () => { + const controller: KubernetesController = module.get(KubernetesController); + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/kubernetes/kubernetes.controller.ts b/apps/api/src/app/project/kubernetes/kubernetes.controller.ts new file mode 100644 index 000000000..c3049d15c --- /dev/null +++ b/apps/api/src/app/project/kubernetes/kubernetes.controller.ts @@ -0,0 +1,43 @@ +import { Controller, Get, HttpStatus, Logger, Param } from '@nestjs/common'; +import { ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTags } from '@nestjs/swagger'; +import { KubernetesService } from './kubernetes.service'; +import { Roles, RolesEnum } from '../../auth/decorators'; + +@ApiOAuth2Auth(['read']) +@ApiUseTags('Project', 'Kubernetes') +@ApiResponse({ status: HttpStatus.OK, description: 'Found' }) +@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Not Found' }) +@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' }) +@ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden' }) +@Controller('kubernetes') +export class KubernetesController { + readonly logger = new Logger(KubernetesController.name); + + constructor(private readonly kubernetesService: KubernetesService) {} + + // @ApiExcludeEndpoint() + @Roles(RolesEnum.ADMIN) + @ApiUseTags('Admin') + @ApiOperation({ title: 'Refresh kubernetes clusters from database' }) + @Get('refresh') + refreshClusters(): Promise { + return this.kubernetesService.refreshClusters(); + } + + @Roles(RolesEnum.ADMIN) + @ApiUseTags('Admin') + @ApiOperation({ title: 'Get all namespaces in a cluster by cluster name' }) + @ApiResponse({ status: HttpStatus.OK, description: 'All records', /* type: T, */ isArray: true }) + @Get(':cluster') + listNamespaces(@Param('cluster') cluster: string): Promise { + return this.kubernetesService.listNamespaces(cluster); + } + + @ApiOperation({ title: 'Find one namespace in a cluster by namespace name' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Found one record' /*, type: T*/ }) + @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) + @Get(':cluster/:namespace') + getNamespace(@Param('cluster') cluster: string, @Param('namespace') namespace: string): Promise { + return this.kubernetesService.getNamespace({ cluster, namespace }); + } +} diff --git a/apps/api/src/app/project/kubernetes/kubernetes.service.spec.ts b/apps/api/src/app/project/kubernetes/kubernetes.service.spec.ts new file mode 100644 index 000000000..6b676f43e --- /dev/null +++ b/apps/api/src/app/project/kubernetes/kubernetes.service.spec.ts @@ -0,0 +1,15 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { KubernetesService } from './kubernetes.service'; + +describe('KubernetesService', () => { + let service: KubernetesService; + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [KubernetesService], + }).compile(); + service = module.get(KubernetesService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/kubernetes/kubernetes.service.ts b/apps/api/src/app/project/kubernetes/kubernetes.service.ts new file mode 100644 index 000000000..7d8a8b326 --- /dev/null +++ b/apps/api/src/app/project/kubernetes/kubernetes.service.ts @@ -0,0 +1,444 @@ +import { + BadRequestException, + ConflictException, + HttpException, + HttpStatus, + Injectable, + Logger, + NotFoundException, + OnModuleInit, + UnauthorizedException, +} from '@nestjs/common'; +import * as Api from 'kubernetes-client'; +import { ConfigService } from '../../config'; +import { ClusterService } from '../cluster/cluster.service'; +import { KubeContext } from '../interfaces/kube-context'; +import { User } from '../../auth'; + +const Client = Api.Client1_10; +const config = Api.config; + +@Injectable() +export class KubernetesService implements OnModuleInit { + private readonly logger = new Logger(KubernetesService.name); + private clusters: Map; + + constructor(private readonly appConfig: ConfigService, private readonly clusterService: ClusterService) {} + + async onModuleInit() { + await this.refreshClusters(); + // @ts-ignore + // for (const [key, client] of this.clusters.entries()) { + // try { + // await client.loadSpec(); + // } catch (err) { + // console.error(`Unable to connect to ${key}`, err); + // } + // } + } + + /** + * for admin + */ + + async refreshClusters() { + this.logger.log('Refreshing Kubernetes Clusters...'); + const [clusters, size] = await this.clusterService.getAll(); + this.logger.log(`Refreshed ${size} Kubernetes Clusters`); + this.clusters = new Map( + clusters.map<[string, Api.ApiRoot]>(cluster => [ + cluster.name, + new Client({ + config: { + url: cluster.baseUrl, + auth: { + bearer: cluster.token, + }, + insecureSkipTlsVerify: true, + version: 'v1', + promises: true, + }, + version: cluster.version || '1.13', + }), + ]), + ); + } + + async listNamespaces(cluster: string) { + try { + const namespaces = await this.clusters.get(cluster).api.v1.namespaces.get(); + return namespaces.body.items; + } catch (error) { + this.handleError(error); + } + } + + /** + * for any + */ + + async hasNamespace({ cluster, namespace }: KubeContext) { + try { + const foundNamespace = await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .get(); + return !!foundNamespace; + } catch (error) { + if (error.code === 404) { + return false; + } + this.handleError(error); + } + } + + async getNamespace({ cluster, namespace }: KubeContext) { + try { + const response = await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .get(); + return response.body; + } catch (error) { + this.handleError(error); + } + } + + async createNamespace({ cluster, namespace, labels: { name = namespace } }: KubeContext) { + const request = { + body: { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name, + labels: { + namespace, + }, + }, + }, + }; + + try { + return await this.clusters.get(cluster).api.v1.namespaces.post(request); + } catch (error) { + this.handleError(error); + } + } + + async deleteNamespace({ cluster, namespace }: KubeContext) { + try { + return await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .delete(); + } catch (error) { + this.handleError(error); + } + } + + async createServiceAccount({ cluster, namespace }: KubeContext, serviceAccountName: string) { + const request = { + body: { + apiVersion: 'v1', + kind: 'ServiceAccount', + metadata: { + name: serviceAccountName, + }, + }, + }; + + try { + return await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .serviceaccounts.post(request); + } catch (error) { + this.handleError(error); + } + } + + async deleteServiceAccount({ cluster, namespace }: KubeContext, serviceAccountName: string) { + try { + return await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .serviceaccounts(serviceAccountName) + .delete(); + } catch (error) { + this.handleError(error); + } + } + + async createNetworkPolicy({ cluster, namespace }: KubeContext) { + const request = { + body: { + apiVersion: 'networking.k8s.io/v1', + kind: 'NetworkPolicy', + metadata: { name: 'default-deny' }, + spec: { + policyTypes: ['Egress', 'Ingress'], + ingress: [ + { + from: [ + { + namespaceSelector: { + matchLabels: { + namespace, + }, + }, + }, + ], + }, + ], + egress: [ + { + to: [ + { + namespaceSelector: { + matchLabels: { + namespace, + }, + }, + }, + ], + }, + { + ports: [ + { + port: 53, + protocol: 'TCP', + }, + { + port: 53, + protocol: 'UDP', + }, + ], + }, + ], + }, + }, + }; + + try { + return await this.clusters + .get(cluster) + .apis['networking.k8s.io'].v1.namespaces(namespace) + .networkpolicies.post(request); + } catch (error) { + this.handleError(error); + } + } + + async createClusterRoleBindingForServiceAccount( + { cluster, namespace, labels: { name = namespace } }: KubeContext, + serviceAccountName: string, + role: string, + ) { + const request = { + body: { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'RoleBinding', + metadata: { + name: `rb-${name}-${serviceAccountName}-${role}-sa`, + labels: { + created: 'kubeadmin', + }, + }, + roleRef: { + kind: 'ClusterRole', + name: role, + apiGroup: 'rbac.authorization.k8s.io', + }, + subjects: [ + { + kind: 'ServiceAccount', + name: serviceAccountName, + namespace, + }, + ], + }, + }; + + try { + return await this.clusters + .get(cluster) + .apis['rbac.authorization.k8s.io'].v1.namespaces(namespace) + .rolebindings.post(request); + } catch (error) { + this.handleError(error); + } + } + + async createClusterRoleBindingForDashboardUsers({ cluster, namespace, labels: { name = namespace } }: KubeContext, username: string) { + const request = { + body: { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'RoleBinding', + metadata: { + name: `rb-${name}-${username}`, + labels: { + created: 'kubeadmin', + }, + }, + roleRef: { + kind: 'ClusterRole', + name: 'admin', + apiGroup: 'rbac.authorization.k8s.io', + }, + subjects: [ + { + kind: 'User', + name: username, + apiGroup: 'rbac.authorization.k8s.io', + namespace, + }, + ], + }, + }; + + try { + return await this.clusters + .get(cluster) + .apis['rbac.authorization.k8s.io'].v1.namespaces(namespace) + .rolebindings.post(request); + } catch (error) { + this.handleError(error); + } + } + + async createResourceQuotaForNamespace({ cluster, namespace, labels: { name = namespace } }: KubeContext) { + const request = { + body: { + apiVersion: 'v1', + kind: 'ResourceQuota', + metadata: { + name: `rq-${name.toLowerCase()}`, + }, + spec: { + hard: { + cpu: '156', + memory: '720Gi', + 'requests.cpu': '156', + 'requests.memory': '720Gi', + 'limits.cpu': '156', + 'limits.memory': '720Gi', + }, + }, + }, + }; + + try { + return await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .resourcequotas.post(request); + } catch (error) { + this.handleError(error); + } + } + + async createResourceLimitRangeForNamespace({ cluster, namespace, labels: { name = namespace }, }: KubeContext) { + const request = { + body: { + apiVersion: 'v1', + kind: 'LimitRange', + metadata: { + name: `rq-${name.toLowerCase()}`, + }, + spec: { + limits: [ + { + max: { + cpu: '32', + memory: '128Gi', + }, + min: { + cpu: '300m', + memory: '6Mi', + }, + type: 'Pod', + }, + { + default: { + cpu: '500m', + memory: '500Mi', + }, + defaultRequest: { + cpu: '300m', + memory: '300Mi', + }, + type: 'Container', + }, + ], + }, + }, + }; + + try { + return await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .limitranges.post(request); + } catch (error) { + this.handleError(error); + } + } + + async listDeployments({ cluster, namespace }: KubeContext) { + try { + const deployments = await this.clusters + .get(cluster) + .apis.apps.v1.namespaces(namespace) + .deployments.get(); + return deployments.body.items; + } catch (error) { + this.handleError(error); + } + } + + /** + * for me + */ + + async myNamespaces(context: KubeContext) { + const { cluster, namespace } = context; + try { + // this.clusters.get(cluster).setToken(token) + const namespaces = await this.clusters.get(cluster).api.v1.namespaces.get(); + return namespaces.items; + } catch (error) { + this.handleError(error); + } + } + + async myServiceAccounts(context: KubeContext) { + const { cluster, namespace } = context; + try { + const namespaces = await this.clusters + .get(cluster) + .api.v1.namespaces(namespace) + .serviceaccounts.get(); + return namespaces.body.items; + } catch (error) { + this.handleError(error); + } + } + + private handleError(error: Error & { code?: number; statusCode?: number }) { + const message = error.message || 'unknown error'; + const statusCode = error.statusCode || error.code || HttpStatus.I_AM_A_TEAPOT; + console.log(message, statusCode); + switch (statusCode) { + case HttpStatus.CONFLICT: + throw new ConflictException(error.message); + case HttpStatus.UNAUTHORIZED: + throw new UnauthorizedException(error.message); + case HttpStatus.NOT_FOUND: + throw new NotFoundException(error.message); + case HttpStatus.BAD_REQUEST: + throw new BadRequestException(error.message); + default: + throw new HttpException(message, statusCode); + } + } +} diff --git a/apps/api/src/app/project/project.controller.spec.ts b/apps/api/src/app/project/project.controller.spec.ts new file mode 100644 index 000000000..c377e540d --- /dev/null +++ b/apps/api/src/app/project/project.controller.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectController } from './project.controller'; + +describe('Project Controller', () => { + let module: TestingModule; + + beforeAll(async () => { + module = await Test.createTestingModule({ + controllers: [ProjectController], + }).compile(); + }); + it('should be defined', () => { + const controller: ProjectController = module.get(ProjectController); + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts new file mode 100644 index 000000000..4aac2216b --- /dev/null +++ b/apps/api/src/app/project/project.controller.ts @@ -0,0 +1,62 @@ +import { Body, Controller, HttpStatus, Logger, Param, Post, Put } from '@nestjs/common'; +import { ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTags } from '@nestjs/swagger'; +import { ProjectService } from './project.service'; +import { KubernetesService } from './kubernetes/kubernetes.service'; +import { CrudController } from '../core'; +import { Project } from './project.entity'; +import { CreateProjectDto } from './dto/create-project.dto'; +import { CurrentUser, Token, User } from '../auth'; +import { KubeContext } from './interfaces/kube-context'; +import { UpdateProjectDto } from './dto/update-project.dto'; + +@ApiOAuth2Auth(['read']) +@ApiUseTags('Project') +@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' }) +@ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden' }) +@Controller('project') +export class ProjectController extends CrudController { + private readonly logger = new Logger(ProjectController.name); + + constructor(private readonly projectService: ProjectService, private readonly kservice: KubernetesService) { + super(projectService); + } + + // @ApiOperation({ title: 'Find projects with any given groups or userId' }) + // @ApiResponse({ status: HttpStatus.OK, description: 'Found matching records', type: Project, isArray: true }) + // @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'No matching records found' }) + // @Post('/search') + // async search(@Body() filter: SearchProjectDto): Promise { + // return this.projectService.findByUserIdOrGroups(filter); + // } + + @ApiOperation({ title: 'Create new record, Self use only' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'The record has been successfully created.', type: Project }) + @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid input, The response body may contain clues as to what went wrong', }) + @Post() + async create(@Body() entity: CreateProjectDto, @CurrentUser() user: User, @Token() token): Promise { + const context: KubeContext = { cluster: entity.clusterName, namespace: entity.namespace, labels: entity.labels }; + // defer(async () => { }) + // const rs1 = await this.kservice.createNamespace(context); + // const rs2 = await this.kservice.createNetworkPolicy(context); + // const rs3 = await this.kservice.createServiceAccount(context, entity.serviceAccountName); + // const rs4 = await this.kservice.createClusterRoleBindingForServiceAccount(context, entity.serviceAccountName, RoleType.Admin); + // const rs5 = await this.kservice.createClusterRoleBindingForDashboardUsers(context, user.username); + // const rs6 = await this.kservice.createResourceQuotaForNamespace(context); + // const rs7 = await this.kservice.createResourceLimitRangeForNamespace(context); + + return this.projectService.create(entity, user); + } + + @ApiOperation({ title: 'Update an existing record' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'The record has been successfully edited.' }) + @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Invalid input, The response body may contain clues as to what went wrong', + }) + @Put(':id') + async update(@Param('id') id: string, @Body() entity: UpdateProjectDto): Promise { + // TODO check if owner + return super.update(id, entity); + } +} diff --git a/apps/api/src/app/project/project.entity.ts b/apps/api/src/app/project/project.entity.ts new file mode 100644 index 000000000..c640b1f26 --- /dev/null +++ b/apps/api/src/app/project/project.entity.ts @@ -0,0 +1,77 @@ +import { Column, Entity, Index, ManyToOne, RelationId } from 'typeorm'; +import { ApiModelProperty } from '@nestjs/swagger'; +import { Labels } from './dto/labels.dto'; +import { Membership } from './dto/membership'; +import { ResourceQuota } from './dto/resource-quota'; +import { AuditBase } from '../core/entities/audit-base.entity'; +import { Cluster } from './cluster/cluster.entity'; +import { Project as IProject } from '@ngx-starter-kit/models'; + +@Entity() +export class Project extends AuditBase implements IProject { + @ApiModelProperty() + @Index() + @Column() + name: string; + + @ApiModelProperty() + @Column() + description: string; + + @ApiModelProperty() + @Column() + namespace: string; + + @ApiModelProperty() + @Column() + serviceAccountName: string; + + @ApiModelProperty() + @Column() + email: string; + + @ApiModelProperty() + @Column() + username: string; + + @ApiModelProperty() + @Index() + @Column() + billingId: string; + + @ApiModelProperty() + @Column({ default: true }) + isActive?: boolean; + + @ApiModelProperty({ type: Cluster }) + @ManyToOne(type => Cluster, cluster => cluster.projects, { onDelete: 'CASCADE', nullable: false }) + cluster: Cluster; + + @ApiModelProperty({ type: Labels }) + @Column('jsonb') + labels: Labels; + + @ApiModelProperty({ type: String, isArray: true }) + @Column('text', { nullable: true, array: true }) + tags?: Set; + + @ApiModelProperty({ type: Membership, isArray: true }) + @Column('jsonb', { nullable: true }) + memberships?: Set; + + @ApiModelProperty({ type: ResourceQuota }) + @Column('jsonb') + resourceQuota: ResourceQuota; + + @ApiModelProperty({ type: Number, readOnly: true }) + @RelationId((project: Project) => project.cluster) + readonly clusterId?: number; + + @ApiModelProperty({ type: Number, readOnly: true }) + @RelationId((project: Project) => project.createdBy) + readonly createdById?: number; + + @ApiModelProperty({ type: Number, readOnly: true }) + @RelationId((project: Project) => project.updatedBy) + readonly updatedById?: number; +} diff --git a/apps/api/src/app/project/project.module.ts b/apps/api/src/app/project/project.module.ts new file mode 100644 index 000000000..6142574e3 --- /dev/null +++ b/apps/api/src/app/project/project.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { KubernetesController } from './kubernetes/kubernetes.controller'; +import { KubernetesService } from './kubernetes/kubernetes.service'; +import { ClusterController } from './cluster/cluster.controller'; +import { ClusterService } from './cluster/cluster.service'; +import { ProjectController } from './project.controller'; +import { ProjectService } from './project.service'; +import { Cluster } from './cluster/cluster.entity'; +import { Project } from './project.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Cluster, Project])], + controllers: [KubernetesController, ClusterController, ProjectController], + providers: [KubernetesService, ClusterService, ProjectService] +}) +export class ProjectModule {} diff --git a/apps/api/src/app/project/project.service.spec.ts b/apps/api/src/app/project/project.service.spec.ts new file mode 100644 index 000000000..631a7416a --- /dev/null +++ b/apps/api/src/app/project/project.service.spec.ts @@ -0,0 +1,16 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProjectService } from './project.service'; + +describe('ProjectService', () => { + let service: ProjectService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ProjectService], + }).compile(); + service = module.get(ProjectService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/project/project.service.ts b/apps/api/src/app/project/project.service.ts new file mode 100644 index 000000000..fc297752a --- /dev/null +++ b/apps/api/src/app/project/project.service.ts @@ -0,0 +1,83 @@ +import { BadRequestException, Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CrudService, RequestContext } from '../core'; +import { DeepPartial, FindConditions, Repository, UpdateResult } from 'typeorm'; +import { Project } from './project.entity'; +import { User } from '../auth'; +import { KubernetesService } from './kubernetes/kubernetes.service'; +import { ClusterService } from './cluster/cluster.service'; +import { CreateProjectDto } from './dto/create-project.dto'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +@Injectable() +export class ProjectService extends CrudService implements OnModuleInit { + private readonly logger = new Logger(ProjectService.name); + + constructor( + @InjectRepository(Project) private readonly projectRepository: Repository, + private readonly clusterService: ClusterService, + private readonly kservice: KubernetesService, + ) { + super(projectRepository); + } + + async onModuleInit() { + this.logger.log('Initialization... ProjectService'); + } + + // async findByUserIdOrGroups(filter: SearchProjectDto): Promise { + // const cursor = this.projectRepository.createEntityCursor({ + // $or: [ + // { + // bsId: filter.userId, + // }, + // { + // 'memberships.name': filter.userId, + // }, + // { + // memberships: { $elemMatch: { name: filter.userId, type: 'User' } }, + // }, + // { + // memberships: { $elemMatch: { name: { $in: filter.groups }, type: 'Group' } }, + // }, + // ], + // $and: [ + // { + // isActive: true, + // }, + // ], + // }); + // return cursor.limit(50).toArray(); + // } + + async create(entity: DeepPartial, user: User): Promise { + entity.cluster = await this.clusterService.getOne({ name: (entity as CreateProjectDto).clusterName }); + // TODO: should we auto apply current user's username/email or let users create on behalf of others? + if (!entity.username) { + entity.username = user.username; + } + if (!entity.email) { + entity.email = user.email; + } + return await super.create(entity); + } + + /** + * FIXME: workaround as @BeforeUpdate don't work with repository.update(). + */ + async update( + id: string | number | FindConditions, + partialEntity: QueryDeepPartialEntity, + ...options: any[] + ): Promise { + try { + const user = RequestContext.currentUser(); + if (user) { + partialEntity.updatedBy = user; + } + return await this.repository.update(id, partialEntity); + } catch (err /*: WriteError*/) { + throw new BadRequestException(err); + } + } +} diff --git a/apps/api/src/app/shared/eventbus.gateway.ts b/apps/api/src/app/shared/eventbus.gateway.ts index 074e0818c..85345b45a 100644 --- a/apps/api/src/app/shared/eventbus.gateway.ts +++ b/apps/api/src/app/shared/eventbus.gateway.ts @@ -29,15 +29,15 @@ export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGa super(); } - public afterInit(server) {} + afterInit(server) {} - public handleConnection(client: ISocket) { + handleConnection(client: ISocket) { // this.logger.log(`Client connected => ${client.id} ${client.handshake.query.token}`); // TODO do auth here this.clients.push(client); } - public handleDisconnect(client: ISocket) { + handleDisconnect(client: ISocket) { // this.logger.log(`Client disconnected => ${client.id}`); // FIXME: remove any. only needed for docker build this.clients = this.clients.filter(c => (c as any).id !== (client as any).id); @@ -46,14 +46,14 @@ export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGa @UseGuards(WsAuthGuard) @SubscribeMessage('auth') onAuthenticate(client: ISocket, data: any) { - // this.logger.log(`auth => ${client.id} ${client.user.userId}`); + // this.logger.log(`auth => ${client.id} ${client.user.username}`); const event = 'auth'; return { event, status: 'success' }; } @SubscribeMessage('test') onTest(client: ISocket, data: any): Observable> { - // this.logger.log(`test => ${client.id} ${client.user.userId}`); + // this.logger.log(`test => ${client.id} ${client.user.username}`); const event = 'test'; // client.broadcast.emit({event, data}); return of({ event, data }).pipe(delay(1000)); @@ -61,25 +61,25 @@ export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGa @SubscribeMessage('actions') onActions(client: ISocket, action: any) { - // this.logger.log(`actions => ${client.id} ${client.user.userId} ${action.type} ${action.payload}`); + // this.logger.log(`actions => ${client.id} ${client.user.username} ${action.type} ${action.payload}`); this.emit(action.type, action, client.user); } - public sendActionToUser(user: User, action: any): void { + sendActionToUser(user: User, action: any): void { const clients = this.getSocketsForUser(user); const type = this.getActionTypeFromInstance(action); // FIXME: remove any. only needed for docker build clients.forEach(socket => (socket as any).emit(EventBusGateway.ACTIONS, { ...action, type })); } - public sendActionToAll(action: any): void { + sendActionToAll(action: any): void { const type = this.getActionTypeFromInstance(action); // FIXME: remove any. only needed for docker build this.clients.forEach(socket => (socket as any).emit(EventBusGateway.ACTIONS, { ...action, type })); } private getSocketsForUser(user: User): ISocket[] { - return this.clients.filter(c => c.user && c.user.userId === user.userId); + return this.clients.filter(c => c.user && c.user.username === user.username); } private getActionTypeFromInstance(action: any): string { diff --git a/apps/api/src/app/user/email/email.controller.ts b/apps/api/src/app/user/email/email.controller.ts index b7b3eb825..0878ae1bc 100644 --- a/apps/api/src/app/user/email/email.controller.ts +++ b/apps/api/src/app/user/email/email.controller.ts @@ -1,27 +1,30 @@ import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { EmailService } from '../../email'; import { EmailDto } from './dto/email.dto'; -import { ApiOAuth2Auth, ApiUseTags } from '@nestjs/swagger'; +import { ApiOAuth2Auth, ApiUseTags, ApiExcludeEndpoint } from '@nestjs/swagger'; +import { CurrentUser } from '../../auth/decorators'; +import { User } from '../../auth'; @ApiOAuth2Auth(['read']) -@ApiUseTags('Email') +@ApiUseTags('Email', 'User') @Controller('email') export class EmailController { constructor(private readonly emailService: EmailService) {} + // @ApiExcludeEndpoint() @Post() @HttpCode(HttpStatus.CREATED) - async sendEmail(@Body() email: EmailDto): Promise { + async sendEmail(@Body() email: EmailDto, @CurrentUser() user: User): Promise { return this.emailService.sendMail({ - to: 'sumo@demo.com', + to: user.email, subject: email.title, template: 'welcome', // The `.pug` extension is appended automatically. - context: { - // Data to be sent to PugJS template files. + context: { // Data to be sent to PugJS template files. title: email.title, comments: email.comments, name: email.name, }, }); } + } diff --git a/apps/api/src/app/user/profile/profile.controller.ts b/apps/api/src/app/user/profile/profile.controller.ts index e5518139b..790729815 100644 --- a/apps/api/src/app/user/profile/profile.controller.ts +++ b/apps/api/src/app/user/profile/profile.controller.ts @@ -24,7 +24,7 @@ import { ProfileService } from './profile.service'; const ALLOWED_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/bmp', 'image/webp']; @ApiOAuth2Auth(['read']) -@ApiUseTags('Profile') +@ApiUseTags('Profile', 'User') @Controller('profile') export class ProfileController extends CrudController { constructor(private readonly profileService: ProfileService) { @@ -52,7 +52,7 @@ export class ProfileController extends CrudController { type: Profile /*[[Profile], Number]*/, isArray: true, }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get() async findAll(): Promise<[Profile[], number]> { @@ -62,7 +62,7 @@ export class ProfileController extends CrudController { @ApiOperation({ title: 'Find Profile by id. Admins only' }) @ApiResponse({ status: HttpStatus.OK, description: 'Found one record', type: Profile }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' }) - @ApiUseTags('admin') + @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get(':id') async findById(@Param('id') id: string): Promise { diff --git a/apps/api/src/environments/environment.prod.ts b/apps/api/src/environments/environment.prod.ts index cf62b096b..78a52fe78 100644 --- a/apps/api/src/environments/environment.prod.ts +++ b/apps/api/src/environments/environment.prod.ts @@ -44,6 +44,11 @@ export const environment = { templateDir: process.env.EMAIL_TEMPLATE_DIR || `${__dirname}/assets/email-templates`, }, + weather: { + baseUrl: 'https://api.openweathermap.org/data/2.5', + apiKey: '7cb0f6a068d1de4845c49ba22b74d7cc', + }, + // Key generation: https://web-push-codelab.glitch.me webPush: { subject: process.env.VAPID_SUBJECT || 'mailto: sumo@demo.com', @@ -52,30 +57,4 @@ export const environment = { 'BAJq-yHlSNjUqKW9iMY0hG96X9WdVwetUFDa5rQIGRPqOHKAL_fkKUe_gUTAKnn9IPAltqmlNO2OkJrjdQ_MXNg', privateKey: process.env.VAPID_PRIVATE_KEY || 'cwh2CYK5h_B_Gobnv8Ym9x61B3qFE2nTeb9BeiZbtMI', }, - kubernetes: { - CLUSTER1: { - baseUrl: 'https://cluster1:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: process.env.CLUSTER1_SERVICE_ACCOUNT_TOKEN || 'AAAAAAAAAAAA', - }, - CLUSTER2: { - baseUrl: 'https://cluster2:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: process.env.CLUSTER2_SERVICE_ACCOUNT_TOKEN || 'BBBBBBBBBBBB', - }, - CLUSTER3: { - baseUrl: 'https://cluster3:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: process.env.CLUSTER3_SERVICE_ACCOUNT_TOKEN || 'CCCCCCCCCCCCC', - }, - CLUSTER4: { - baseUrl: 'https://cluster4:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: process.env.CLUSTER4_SERVICE_ACCOUNT_TOKEN || 'DDDDDDDDDDDDD', - }, - }, }; diff --git a/apps/api/src/environments/environment.ts b/apps/api/src/environments/environment.ts index 6a81a730e..9626330a7 100644 --- a/apps/api/src/environments/environment.ts +++ b/apps/api/src/environments/environment.ts @@ -30,14 +30,15 @@ export const environment = { auth: { clientId: 'ngxapi', - // issuer: 'http://localhost:8080/auth/realms/ngx', - issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', + issuer: 'http://localhost:8080/auth/realms/ngx', + // issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', }, email: { transport: { host: 'mail.google.com', port: 25, + // secure: false }, defaults: { from: '"sumo demo" ', @@ -45,36 +46,15 @@ export const environment = { templateDir: 'apps/api/src/assets/email-templates', }, + weather: { + baseUrl: 'https://samples.openweathermap.org/data/2.5', + apiKey: 'b6907d289e10d714a6e88b30761fae22', + }, + // Key generation: https://web-push-codelab.glitch.me webPush: { subject: 'mailto: sumo@demo.com', publicKey: 'BAJq-yHlSNjUqKW9iMY0hG96X9WdVwetUFDa5rQIGRPqOHKAL_fkKUe_gUTAKnn9IPAltqmlNO2OkJrjdQ_MXNg', privateKey: 'cwh2CYK5h_B_Gobnv8Ym9x61B3qFE2nTeb9BeiZbtMI', }, - kubernetes: { - CLUSTER1: { - baseUrl: 'https://cluster1:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: 'AAAAAAAAAAAA', - }, - CLUSTER2: { - baseUrl: 'https://cluster2:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: 'BBBBBBBBBBBB', - }, - CLUSTER3: { - baseUrl: 'https://cluster3:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: 'CCCCCCCCCCCCC', - }, - CLUSTER4: { - baseUrl: 'https://cluster4:8080', - version: '1.10', - /* tslint:disable-next-line:max-line-length */ - token: 'DDDDDDDDDDDDD', - }, - }, }; diff --git a/apps/api/src/main.hmr.ts b/apps/api/src/main.hmr.ts index d55f15e2b..260b45830 100755 --- a/apps/api/src/main.hmr.ts +++ b/apps/api/src/main.hmr.ts @@ -28,8 +28,6 @@ async function bootstrap() { .setDescription('Sumo API for Ngx Starter Kit') .setExternalDoc('Github Repo', 'https://github.com/xmlking/ngx-starter-kit/tree/master/apps/api') .setVersion(config.getVersion()) - .addTag('Sumo') - .addTag('External') .setSchemes(config.isProd() ? 'https' : 'http') .addOAuth2( 'implicit', diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index f3e2524fe..e86497dc4 100755 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -29,8 +29,6 @@ async function bootstrap() { .setDescription('Sumo API for Ngx Starter Kit') .setExternalDoc('Github Repo', 'https://github.com/xmlking/ngx-starter-kit/tree/master/apps/api') .setVersion(config.getVersion()) - .addTag('Sumo') - .addTag('External') .setSchemes(config.isProd() ? 'https' : 'http') .addOAuth2( 'implicit', diff --git a/apps/webapp/src/app/app.component.scss b/apps/webapp/src/app/app.component.scss index c4df3ab4c..9d4cc4566 100644 --- a/apps/webapp/src/app/app.component.scss +++ b/apps/webapp/src/app/app.component.scss @@ -1,13 +1,3 @@ -ngx-home-layout { - display: flex; - flex-direction: column; - position: absolute; - top: 56px; - bottom: 0; - left: 0; - right: 0; -} - ngx-root > ngx-sidenav { flex: 1; } diff --git a/apps/webapp/src/assets/data/subscription.json b/apps/webapp/src/assets/data/subscription.json index a7d3e15dc..3a2b20b88 100644 --- a/apps/webapp/src/assets/data/subscription.json +++ b/apps/webapp/src/assets/data/subscription.json @@ -5,7 +5,7 @@ "endpoint": "https://fcm.googleapis.com/fcm/send/c8eq7G-b2mc:APA91bFp2Gof...", "auth": "yu_cVYbAPA91bFp2Gof", "p256dh": "BIt3gcVYbAPA91bFp2GofB0Y7N-zvcVYbAPA91bFp2Gof-0jqPlmfIwtcVYbAPA91bFp2GofYBgvk", - "userId": "sumo3", + "username": "sumo3", "topics": ["topic_1", "topic_2"], "createdAt": "2018-11-21T18:50:45.325Z", "updatedAt": "2018-11-21T18:50:45.325Z" diff --git a/apps/webapp/src/environments/environment.mock.ts b/apps/webapp/src/environments/environment.mock.ts index ef64f525e..27049a113 100644 --- a/apps/webapp/src/environments/environment.mock.ts +++ b/apps/webapp/src/environments/environment.mock.ts @@ -11,7 +11,7 @@ export const environment: IEnvironment = { WS_EVENT_BUS_URL: 'ws://localhost:3000/eventbus', auth: { - issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', clientId: 'ngxapp', + issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', }, }; diff --git a/apps/webapp/src/environments/environment.prod.ts b/apps/webapp/src/environments/environment.prod.ts index fcbe4da55..0019a9daf 100644 --- a/apps/webapp/src/environments/environment.prod.ts +++ b/apps/webapp/src/environments/environment.prod.ts @@ -11,7 +11,7 @@ export const environment: IEnvironment = { WS_EVENT_BUS_URL: 'ws://localhost:3000/eventbus', auth: { - issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', clientId: 'ngxapp', + issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', }, }; diff --git a/apps/webapp/src/environments/environment.ts b/apps/webapp/src/environments/environment.ts index f2508f001..65eabf755 100644 --- a/apps/webapp/src/environments/environment.ts +++ b/apps/webapp/src/environments/environment.ts @@ -14,8 +14,9 @@ export const environment: IEnvironment = { WS_EVENT_BUS_URL: 'ws://localhost:3000/eventbus', auth: { - issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', clientId: 'ngxapp', + issuer: 'http://localhost:8080/auth/realms/ngx', + // issuer: 'https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', }, }; diff --git a/greenkeeper.json b/greenkeeper.json new file mode 100644 index 000000000..39a7cce84 --- /dev/null +++ b/greenkeeper.json @@ -0,0 +1,29 @@ +{ + "groups": { + "default": { + "packages": [ + "libs/app-confirm/package.json", + "libs/breadcrumbs/package.json", + "libs/chat-box/package.json", + "libs/clap/package.json", + "libs/context-menu/package.json", + "libs/draggable/package.json", + "libs/image-comparison/package.json", + "libs/json-diff/package.json", + "libs/led/package.json", + "libs/loading-overlay/package.json", + "libs/ngx-utils/package.json", + "libs/notifications/package.json", + "libs/oidc/package.json", + "libs/scroll-to-top/package.json", + "libs/scrollbar/package.json", + "libs/socketio-plugin/package.json", + "libs/svg-viewer/package.json", + "libs/theme-picker/package.json", + "libs/tree/package.json", + "libs/utils/package.json", + "package.json" + ] + } + } +} diff --git a/lerna.json b/lerna.json new file mode 100644 index 000000000..3c54489c3 --- /dev/null +++ b/lerna.json @@ -0,0 +1,16 @@ +{ + "lerna": "3.13.0", + "npmClient": "yarn", + "version": "0.0.0", + "packages": ["libs/*", "apps/*"], + "useWorkspaces": true, + "command": { + "exec": { + "scope": ["@ngx-starter-kit/*"] + }, + "clean": { + "yes": true + } + }, + "npmClientArgs": ["--no-package-lock"] +} diff --git a/libs/admin/src/lib/containers/subscriptions/subscriptions.component.ts b/libs/admin/src/lib/containers/subscriptions/subscriptions.component.ts index 097e270cd..020b5ce9a 100644 --- a/libs/admin/src/lib/containers/subscriptions/subscriptions.component.ts +++ b/libs/admin/src/lib/containers/subscriptions/subscriptions.component.ts @@ -47,7 +47,7 @@ export class SubscriptionsComponent extends EntitiesComponent({ property: 'id', header: 'No.' }), - new EntityColumnDef({ property: 'userId', header: 'User' }), + new EntityColumnDef({ property: 'username', header: 'User' }), new EntityColumnDef({ property: 'topics', header: 'Topics' }), new EntityColumnDef({ property: 'createdAt', @@ -65,7 +65,7 @@ export class SubscriptionsComponent extends EntitiesComponent confirmed === true), mergeMap(_ => super.delete(item)), tap(_ => { diff --git a/libs/admin/src/lib/models/subscription.model.ts b/libs/admin/src/lib/models/subscription.model.ts index 489279705..d1d73a088 100644 --- a/libs/admin/src/lib/models/subscription.model.ts +++ b/libs/admin/src/lib/models/subscription.model.ts @@ -5,7 +5,7 @@ export class Subscription extends Entity { endpoint: string; auth: string; p256dh: string; - userId: string; + username: string; topics: string[]; createdAt?: Date; updatedAt?: Date; diff --git a/libs/auth/src/lib/oauth.config.ts b/libs/auth/src/lib/oauth.config.ts index ad1f18c76..1d4a62ca0 100644 --- a/libs/auth/src/lib/oauth.config.ts +++ b/libs/auth/src/lib/oauth.config.ts @@ -17,7 +17,7 @@ const authConfig: AuthConfig = { // set the scope for the permissions the client should request // The first three are defined by OIDC. The 4th is a usecase-specific one - scope: 'openid profile email offline_access', + scope: 'openid profile email', showDebugInformation: true, }; diff --git a/libs/core/src/lib/services/google-analytics.service.ts b/libs/core/src/lib/services/google-analytics.service.ts index a68c30278..de3c5f864 100644 --- a/libs/core/src/lib/services/google-analytics.service.ts +++ b/libs/core/src/lib/services/google-analytics.service.ts @@ -38,9 +38,9 @@ export class GoogleAnalyticsService { /** * set user after login success. */ - public setUsername(userId: string) { + public setUsername(username: string) { if (typeof ga === 'function') { - ga('set', 'userId', userId); + ga('set', 'userId', username); } } diff --git a/libs/home/src/lib/components/header/header.component.html b/libs/home/src/lib/components/header/header.component.html index 3619fd43c..90a522792 100644 --- a/libs/home/src/lib/components/header/header.component.html +++ b/libs/home/src/lib/components/header/header.component.html @@ -49,7 +49,8 @@ diff --git a/libs/models/src/index.ts b/libs/models/src/index.ts index 70e2ed22e..0513f5655 100644 --- a/libs/models/src/index.ts +++ b/libs/models/src/index.ts @@ -4,3 +4,10 @@ export { Image } from './lib/image.model'; export { Gender } from './lib/gender.enum'; export { ImageType } from './lib/image-type.enum'; export { AccountSourceType } from './lib/account-source-type.enum'; +export { ZoneType } from './lib/zone-type.enum'; +export { EnvironmentType } from './lib/environment-type.enum'; +export { Labels } from './lib/labels.model'; +export { Project } from './lib/project.model'; +export { Cluster } from './lib/cluster.model'; +export * from './lib/membership.model'; +export * from './lib/resource-quota.model'; diff --git a/libs/models/src/lib/account-source-type.enum.ts b/libs/models/src/lib/account-source-type.enum.ts index 4f966d2df..f334e1765 100644 --- a/libs/models/src/lib/account-source-type.enum.ts +++ b/libs/models/src/lib/account-source-type.enum.ts @@ -1,5 +1,5 @@ export enum AccountSourceType { - msId, + bsId, hsId, gitHub, } diff --git a/libs/models/src/lib/cluster.model.ts b/libs/models/src/lib/cluster.model.ts new file mode 100644 index 000000000..c475358be --- /dev/null +++ b/libs/models/src/lib/cluster.model.ts @@ -0,0 +1,11 @@ +export interface Cluster { + id?: number | string; + name: string; + ver: string; + baseUrl: string; + token?: string; + createdAt?: Date; + updatedAt?: Date; + readonly createdById?: number | string; + readonly updatedById?: number | string; +} diff --git a/libs/models/src/lib/environment-type.enum.ts b/libs/models/src/lib/environment-type.enum.ts new file mode 100644 index 000000000..49f343e99 --- /dev/null +++ b/libs/models/src/lib/environment-type.enum.ts @@ -0,0 +1,4 @@ +export enum EnvironmentType { + Prod = 'Prod', + NonProd = 'NonProd', +} diff --git a/libs/models/src/lib/labels.model.ts b/libs/models/src/lib/labels.model.ts new file mode 100644 index 000000000..ef050579f --- /dev/null +++ b/libs/models/src/lib/labels.model.ts @@ -0,0 +1,8 @@ +import { EnvironmentType } from './environment-type.enum'; +import { ZoneType } from './zone-type.enum'; + +export interface Labels { + env: EnvironmentType; + zone: ZoneType; + name?: string; +} diff --git a/libs/models/src/lib/membership.model.ts b/libs/models/src/lib/membership.model.ts new file mode 100644 index 000000000..170c1458e --- /dev/null +++ b/libs/models/src/lib/membership.model.ts @@ -0,0 +1,15 @@ +export enum RoleType { + Admin = 'Admin', + Developer = 'Developer', +} + +export enum MembershipType { + User = 'User', + Group = 'Group', +} + +export interface Membership { + name: string; + type: MembershipType; + roles: Set; +} diff --git a/libs/models/src/lib/project.model.ts b/libs/models/src/lib/project.model.ts new file mode 100644 index 000000000..7fb7af97a --- /dev/null +++ b/libs/models/src/lib/project.model.ts @@ -0,0 +1,26 @@ +import { ResourceQuota } from './resource-quota.model'; +import { Cluster } from './cluster.model'; +import { Labels } from './labels.model'; +import { Membership } from './membership.model'; + +export interface Project { + id?: number | string; + name: string; + description: string; + namespace: string; + serviceAccountName: string; + email: string; + username: string; + billingId: string; + isActive?: boolean; + cluster?: Cluster; + labels: Labels; + tags?: Set; + memberships?: Set; + resourceQuota: ResourceQuota; + createdAt?: Date; + updatedAt?: Date; + readonly clusterId?: number; + readonly createdById?: number | string; + readonly updatedById?: number | string; +} diff --git a/libs/models/src/lib/resource-quota.model.ts b/libs/models/src/lib/resource-quota.model.ts new file mode 100644 index 000000000..042f06c2c --- /dev/null +++ b/libs/models/src/lib/resource-quota.model.ts @@ -0,0 +1,17 @@ +export interface Requests { + cpu: number; + gpu: number; + memory: number; +} + +export interface Limits { + cpu: number; + memory: number; +} + +export interface ResourceQuota { + requests: Requests; + limits: Limits; + cpu?: number; + memory?: number; +} diff --git a/libs/models/src/lib/user.model.ts b/libs/models/src/lib/user.model.ts index 637d41c09..33162d6d7 100644 --- a/libs/models/src/lib/user.model.ts +++ b/libs/models/src/lib/user.model.ts @@ -6,10 +6,10 @@ export interface User { firstName: string; lastName: string; email: string; - userId: string; - createdAt?: Date; - updatedAt?: Date; + username: string; images?: Image[]; profile?: Profile; - profileId?: number; + readonly profileId?: number; + readonly createdAt?: Date; + readonly updatedAt?: Date; } diff --git a/libs/models/src/lib/zone-type.enum.ts b/libs/models/src/lib/zone-type.enum.ts new file mode 100644 index 000000000..8f5d57184 --- /dev/null +++ b/libs/models/src/lib/zone-type.enum.ts @@ -0,0 +1,4 @@ +export enum ZoneType { + DMZ = 'DMZ', + Core = 'Core', +} diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 85e65f4ab..05b37cd26 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -3,4 +3,5 @@ export { waitUntil } from './lib/wait-until'; export { DeepPartial } from './lib/deep-partial'; export { ObjectLiteral } from './lib/object-literal'; export { delayAtLeast } from './lib/delay-at-least'; +export { fromAsyncIterator } from './lib/from-async-iterator' export * from './lib/require-multi'; diff --git a/libs/utils/src/lib/from-async-iterator.ts b/libs/utils/src/lib/from-async-iterator.ts new file mode 100644 index 000000000..0bbf7a1eb --- /dev/null +++ b/libs/utils/src/lib/from-async-iterator.ts @@ -0,0 +1,25 @@ +import { Observable, Subscriber, Unsubscribable } from 'rxjs'; + +export function fromAsyncIterator(iterator: AsyncIterator): Observable { + return new Observable( + (subscriber: Subscriber): Unsubscribable => { + async function forwardValues(): Promise { + try { + for (let next = await iterator.next(); !subscriber.closed && !next.done; ) { + subscriber.next(next.value); + next = await iterator.next(); + } + subscriber.complete(); + } catch (e) { + subscriber.error(e); + } + } + + forwardValues(); + + return { + unsubscribe: () => {}, // FIXME memory leak? + }; + }, + ); +} diff --git a/package-lock.json b/package-lock.json index 866adb714..3c746d51f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@angular-devkit/architect": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.3.tgz", - "integrity": "sha512-89VL75bq3+h3m0jhzWNqXqW+HQcrihnM3i6eiUE6P81LcllP159JMlusAvB1LHLNc6Cc62wTq4BJr7KDILkPOA==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.4.tgz", + "integrity": "sha512-wJF8oz8MurtpFi0ik42bkI2F5gEnuOe79KHPO1i3SYfdhEp5NY8igVKZ6chB/eq4Ml50aHxas8Hh9ke12K+Pxw==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.4", "rxjs": "6.3.3" }, "dependencies": { @@ -26,16 +26,16 @@ } }, "@angular-devkit/build-angular": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.3.tgz", - "integrity": "sha512-UxD6UR/tXypMA4lqCiXLtcStI4wuIHLOJLwADmazndFjg1oLqH1onO6UQPHJ1drAUl+AzA5zTQZHzWYokxaLtg==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.5.tgz", + "integrity": "sha512-xJq46Jz7MMyprcJ4PflJjPtJ+2OVqbnz6HwtUyJLwYXmC0ldWnhGiNwn+0o7Em40Ol8gf2TYqcDGcSi5OyOZMg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/build-optimizer": "0.13.3", - "@angular-devkit/build-webpack": "0.13.3", - "@angular-devkit/core": "7.3.3", - "@ngtools/webpack": "7.3.3", + "@angular-devkit/architect": "0.13.5", + "@angular-devkit/build-optimizer": "0.13.5", + "@angular-devkit/build-webpack": "0.13.5", + "@angular-devkit/core": "7.3.5", + "@ngtools/webpack": "7.3.5", "ajv": "6.9.1", "autoprefixer": "9.4.6", "circular-dependency-plugin": "5.0.2", @@ -78,6 +78,40 @@ "webpack-subresource-integrity": "1.1.0-rc.6" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.5.tgz", + "integrity": "sha512-ouqDu5stZA2gsWnbKMThDfOG/D6lJQaLL+oGEoM5zfnKir3ctyV5rOm73m2pDYUblByTCb+rkj5KmooUWpnV1g==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.5", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/build-webpack": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.5.tgz", + "integrity": "sha512-/abR1cxCLiRJciaW0Dc0RYNbYQIhHFut1r1Dv8xx7He2/wYgCzGsYl9EeFm48Nrw62/9rIPJxhZoZtcf1Mrocg==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.5", + "@angular-devkit/core": "7.3.5", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/core": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz", + "integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", @@ -96,17 +130,40 @@ } }, "@angular-devkit/build-ng-packagr": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.13.3.tgz", - "integrity": "sha512-/jnabl9gALEACrBr7+yZNbIfvuqHy6M05QidD+1jNCh8jirGSCMEKqhvUg1m5tAMt32ZcH8r1GGU0jlfa6L39A==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.13.5.tgz", + "integrity": "sha512-6lOgrs7uUA1mZCggQukibsH6fe6EkwYVig6oDLNkHHFCk4H4RviZZuUMgjwXkC7GQ8sOvsBSe8H+Sr2ROU7GzA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/core": "7.3.3", + "@angular-devkit/architect": "0.13.5", + "@angular-devkit/core": "7.3.5", "rxjs": "6.3.3", "semver": "5.6.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.5.tgz", + "integrity": "sha512-ouqDu5stZA2gsWnbKMThDfOG/D6lJQaLL+oGEoM5zfnKir3ctyV5rOm73m2pDYUblByTCb+rkj5KmooUWpnV1g==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.5", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/core": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz", + "integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -119,9 +176,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.3.tgz", - "integrity": "sha512-lxM1icVFy3jyoQfWEGW8TG1M7LTl/Djc98MFBYp/lXoVo2JZoLxy7eo51sRuJFaB7/0mgMP2gs0FcU/Lr4gK+Q==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.5.tgz", + "integrity": "sha512-bkyKYplkUnWCbXfDuS0gFuPDoi9OEUNRBtvYtY3rgE3XKSAJBjV+KLgoXSSpLL6ucLDx6gOyDXitUFLiRCDMqg==", "dev": true, "requires": { "loader-utils": "1.2.3", @@ -145,13 +202,13 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.3.tgz", - "integrity": "sha512-o2ymctVCuz5GhKJH3LO1sl3AUbA4j7zlrqSGB5ToVRBn3GckJJnmfCZzr2SX5Ya4VofxVsIidsiZcawy4FpB2w==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.4.tgz", + "integrity": "sha512-W5baPrsNUUyeD5K9ZjiTfiDsytBoqDvGDMKRUO9XWV8xF8LYF2ttsBQxlJK7SKkMyJXcjmiHhdkMq5wgRE7n0A==", "dev": true, "requires": { - "@angular-devkit/architect": "0.13.3", - "@angular-devkit/core": "7.3.3", + "@angular-devkit/architect": "0.13.4", + "@angular-devkit/core": "7.3.4", "rxjs": "6.3.3" }, "dependencies": { @@ -167,9 +224,10 @@ } }, "@angular-devkit/core": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.3.tgz", - "integrity": "sha512-fosULDtMoDWrOyUzTmBkJccOy7zodo02kENyKai7vOv9EWfv9jytkVdNc+jl0ys9OE2QadvSYBo49jhnZxFXfQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.4.tgz", + "integrity": "sha512-MBfen51iOBKfK4tlg5KwmPxePsF1QoFNUMGLuvUUwPkteonrGcupX1Q7NWTpf+HA+i08mOnZGuepeuQkD12IQw==", + "dev": true, "requires": { "ajv": "6.9.1", "chokidar": "2.0.4", @@ -182,6 +240,7 @@ "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -189,11 +248,12 @@ } }, "@angular-devkit/schematics": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.3.tgz", - "integrity": "sha512-SdDq9eKwceb6WLwci1fywtZ/kARR5CYyzi5dZIR1lOxrz00682uUBqH/X39mKdqc6eVqR7rtPceqNm6nQpOIMg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.4.tgz", + "integrity": "sha512-BLI4MDHmpzw+snu/2Dw1nMmfJ0VAARTbU6DrmzXyl2Se45+iE/tdRy4yNx3IfHhyoCrVZ15R0y9CXeEsLftlIg==", + "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.4", "rxjs": "6.3.3" }, "dependencies": { @@ -201,6 +261,7 @@ "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -208,9 +269,9 @@ } }, "@angular/animations": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.0-beta.5.tgz", - "integrity": "sha512-ZJK0fGDcF0yAV7qdcvOyxllGiIYgs+h++iS407DppEIt7QkW3wwLnJcdZWcXjlcLqB6ZfZ1tR9F2YSm6FrmZqQ==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.0-beta.7.tgz", + "integrity": "sha512-5eILbSD3Ytc01M2sNXIOJI7x2MadeVo4eyYyATOg38jgz4DFm0up/DwSctMt+tbpWHEo2oLCrBR54S42SG4C7Q==", "requires": { "tslib": "^1.9.0" } @@ -225,16 +286,16 @@ } }, "@angular/cli": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.0.0-beta.2.tgz", - "integrity": "sha512-WFV0ge3UNgHL+8b9GJijmq6Lg4F9eWRaSM0Fn+w7UDHwke0m7c9wonOF3BwfULns3KJJAsWDF5KREQloxlz+bg==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.0.0-beta.5.tgz", + "integrity": "sha512-gYo62l1Q/IkY/0sSZwmFyJlOmgqBOQQ2QF3BwxZGMrg+KbEoFUgz+x2xmD558y2KE8O5LnbB0zeGpwSU9IFe9A==", "dev": true, "requires": { - "@angular-devkit/architect": "0.14.0-beta.2", - "@angular-devkit/core": "8.0.0-beta.2", - "@angular-devkit/schematics": "8.0.0-beta.2", - "@schematics/angular": "8.0.0-beta.2", - "@schematics/update": "0.14.0-beta.2", + "@angular-devkit/architect": "0.14.0-beta.5", + "@angular-devkit/core": "8.0.0-beta.5", + "@angular-devkit/schematics": "8.0.0-beta.5", + "@schematics/angular": "8.0.0-beta.5", + "@schematics/update": "0.14.0-beta.5", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "inquirer": "6.2.2", @@ -246,47 +307,58 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.14.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.14.0-beta.2.tgz", - "integrity": "sha512-keMzO353FYhrquPk9DT9XcIRbXxdZqhnWkE9FIFLJ5m6foGP5nA3yDBBxGsfKlshxFj56naIrlepA8rDcZAVAA==", + "version": "0.14.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.14.0-beta.5.tgz", + "integrity": "sha512-AR186t8mpQisJtGysUo73PY4MM9tkM+V8l6Rj3v66sihZJ7PIkcA93AtywUtl7GGS21BKfO6LJ4PaqxiPKV9/g==", "dev": true, "requires": { - "@angular-devkit/core": "8.0.0-beta.2", - "rxjs": "6.3.3" + "@angular-devkit/core": "8.0.0-beta.5", + "rxjs": "6.4.0" } }, "@angular-devkit/core": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.0-beta.2.tgz", - "integrity": "sha512-M8HNPLlmQ4zANIjEN4d7Ua+2qXtWvpSnXJO1bEefqD+usiqBJiaVj8JtINVVfkLuRW97dWibz3JsdKxyed6iaw==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.0-beta.5.tgz", + "integrity": "sha512-ob5HpUusC6Q/TsC1gGgeBvG1JZ53SYdCKBDPcLgpOAg1hAXKrbmmUGe4Y+UQtRSVYPEKceXv3L0cmqcjelBHXQ==", "dev": true, "requires": { - "ajv": "6.9.1", + "ajv": "6.10.0", "chokidar": "2.1.2", "fast-json-stable-stringify": "2.0.0", - "rxjs": "6.3.3", + "rxjs": "6.4.0", "source-map": "0.7.3" } }, "@angular-devkit/schematics": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.0.0-beta.2.tgz", - "integrity": "sha512-0CJb88akw1ozUwHHSWh79aa4sHmE9ZqFawzYKHKysR34LHJB83iER1ijyEure4gqGveptF9ufnPiynFtsZO8hg==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.0.0-beta.5.tgz", + "integrity": "sha512-4WQy5OeiLwGSswsAzdGS3yhfjlHLeRLHE60+I+EeYmdipggjj04WBohNduzypb1LZzqfKctduZdLv0od3N3Nkw==", "dev": true, "requires": { - "@angular-devkit/core": "8.0.0-beta.2", - "rxjs": "6.3.3" + "@angular-devkit/core": "8.0.0-beta.5", + "rxjs": "6.4.0" } }, "@schematics/angular": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.0.0-beta.2.tgz", - "integrity": "sha512-HNpt26xGA1WjkkKR0M7ZAGX5FyNPs++v57xwiLSlJuiS91G20nH72tK66XYs/fU5XtpsBLKny0zx9pTq3DzREw==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.0.0-beta.5.tgz", + "integrity": "sha512-Jjp6ntqVwJFYQejUEOL/52khIHPKjE6oQtmkKoefkp9dSBtpvIi7OgzhzDbVH5IAxZHzf4u19eDiSSBYtwHw1Q==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.0.0-beta.5", + "@angular-devkit/schematics": "8.0.0-beta.5" + } + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "dev": true, "requires": { - "@angular-devkit/core": "8.0.0-beta.2", - "@angular-devkit/schematics": "8.0.0-beta.2", - "typescript": "3.2.4" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "chokidar": { @@ -314,44 +386,29 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "typescript": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", - "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", - "dev": true } } }, "@angular/common": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.0-beta.5.tgz", - "integrity": "sha512-PYXa4pS4hz0Hnw6Zhdq3PK7zap2WErvyUH816ABWEcQZIeZCE6uMfGyy3p/+/wBBF13OT7DvWxmDZkKYWZ6v/w==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.0-beta.7.tgz", + "integrity": "sha512-WuLZ6PwNKvDrU361VeVv7G2UDgHI3gO3TObF0KQOREKJpS4HulOFEb+DgyLTKy0RVFWtcerK7s8TCV9DkB0KRw==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.0-beta.5.tgz", - "integrity": "sha512-2+aFiwdh9t9wJDISbLERFiNId6JB49+Uy2O9LSrNHnHBe9oov3OU6IE3hlUI7vEg6NXZeIJHp2Q3v0hhGjVcVg==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.0-beta.7.tgz", + "integrity": "sha512-AHH1CeVMXa9H5dEDQDhzbJIpdEBkJIHx/+yHKDlQ81SQwPoSkURhop3ma2LtrwPbi4WOqWSrPO9VYVi8+TrLpQ==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler-cli": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.0.0-beta.5.tgz", - "integrity": "sha512-fhpFL5FGu2XRn/7jS+H3J3u5sXhOn3yUJOXrTzxktLzse+xYJS2TTInWx+Fv4yu7AfLwpYVRgWbwE/Dg/YIUYw==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.0.0-beta.7.tgz", + "integrity": "sha512-bs93eQGxHt3a2vLaba/PtBuqlFkcbrmdyA4a9g7ZmYPpFFUxUxD1OZQ4ktQCEkPgNwzYb01HzjQXdvpRpFxt5Q==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -555,9 +612,9 @@ } }, "@angular/core": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.0-beta.5.tgz", - "integrity": "sha512-1D382A8veZZ1IsKNqgLRZM6H9e1EV9mp5aYm88z1nUiPo119BtG2bnbSDG0NV9gRHDuuOMu37O/m/Y6Duet/GQ==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.0-beta.7.tgz", + "integrity": "sha512-jLK54JZi4z7PBKOKbX5b5VWVdxOy6FVJp/+1MCJnonKPH6ckFpzyUFI9QDU2rY2mk4kMamiJ2NRT19UAzuq9sw==", "requires": { "tslib": "^1.9.0" } @@ -571,17 +628,17 @@ } }, "@angular/forms": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.0-beta.5.tgz", - "integrity": "sha512-WntHBOy13vyaLJ3vQ/m72+r4MFZlC2PafXg3PEGuXFSk5eP/Cx/p+l18wnSZelZb+EfbZWXoSnTfl4L65Ko5oQ==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.0-beta.7.tgz", + "integrity": "sha512-BPwagceI5CB1HGj//jTuuaaq7l/vf//8/IGPvQejnJACVpzB0sRqE2HrRjfvTNYzD0GV/pdffcPWRx2yoLvueg==", "requires": { "tslib": "^1.9.0" } }, "@angular/language-service": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.0.0-beta.5.tgz", - "integrity": "sha512-YbNWi5HPJ/arqcjppaFJPnkh4LvRTz6SwpXq1IM/AsMpX4tuxFWasLSBxecz7KS4wQecyx36G84s3vF0wJCpQQ==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.0.0-beta.7.tgz", + "integrity": "sha512-GCL3QoT42yDg4raPLHcwf3bJlOBamufeARXnqW2nRojuerdbNMSfjob1grPeKbi+kL+8CEeGNq9yYdig2Fkotw==", "dev": true }, "@angular/material": { @@ -593,33 +650,54 @@ } }, "@angular/platform-browser": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.0-beta.5.tgz", - "integrity": "sha512-iOb3KOnrFDHR7zHtpR9dgxvA7/I65myoDN15ReWA9d4BeOQCNZZSYkP6WPI7YesYvJed3OHtrkJTJMu66Vu6cw==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.0-beta.7.tgz", + "integrity": "sha512-6zMg1QZFcvAgpwc/Tz0xtm8N9AYtLKR6mMCTAZWR7J7LCh2lSoa7RT+dF/vWkvsbBfTgaaDPAkpZm8ky0yho8Q==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.0-beta.5.tgz", - "integrity": "sha512-bIXZmqnxmfDb/SEkq3oexkRnT9bkk+5umVLJcP9Hcts8LvBb9WpW8UAWGL+stiOO3HQ4X/ZfHF+2C5JlHQFSww==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.0-beta.7.tgz", + "integrity": "sha512-7lgDDEbs1mC0mvSpqRRTcaiGYT+I1lp0HYk7JIFyk49DvborX99Xk3OuHaZZ4thUJkV1gnpaADAU3lLH3NFf3Q==", "requires": { "tslib": "^1.9.0" } }, "@angular/pwa": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-0.13.3.tgz", - "integrity": "sha512-j0e8g0ANkRozgBTDdt7WUTWq6ZNzXiq9K6rH16Kf5Dv4XeOdjv3FvDcGaCxmplr6tt06ulRqYs8uvkiPsxHkNQ==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-0.13.5.tgz", + "integrity": "sha512-4/ECtVAHFYw0RgrS4vil11aMa1mmoDCEzdSPOrhwHqhjDI8qy8TIyHV6o/Qhyztqjnc9gv6le5Z7yA4z8Xpu7g==", "requires": { - "@angular-devkit/core": "7.3.3", - "@angular-devkit/schematics": "7.3.3", - "@schematics/angular": "7.3.3", + "@angular-devkit/core": "7.3.5", + "@angular-devkit/schematics": "7.3.5", + "@schematics/angular": "7.3.5", "parse5-html-rewriting-stream": "5.1.0", "rxjs": "6.3.3" }, "dependencies": { + "@angular-devkit/core": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz", + "integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==", + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "@angular-devkit/schematics": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.5.tgz", + "integrity": "sha512-BFCCkwRMBC4aFlngaloi1avCTgGrl1MFc/0Av2sCpBh/fdm1FqSVzmOiTfu93dehRVVL/bTrA2qj+xpNsXCxzA==", + "requires": { + "@angular-devkit/core": "7.3.5", + "rxjs": "6.3.3" + } + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -631,17 +709,17 @@ } }, "@angular/router": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.0-beta.5.tgz", - "integrity": "sha512-j5lyC+R2GYN1XIpAWgM1Y485pxZwy5OpFEXWZg3tAcm1ZepHB/8uRx2PBg3dn77z/uaP8J/wbjqWLldxTx6KFA==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.0-beta.7.tgz", + "integrity": "sha512-9M/XD1PvwHpTDjKLVya7JCkeXJ/Iq9G4hqHseo7tAm/YTHPBb1n8oTy/uKlI2zRfv2AuNLUApUbCucM+PjJoYA==", "requires": { "tslib": "^1.9.0" } }, "@angular/service-worker": { - "version": "8.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-8.0.0-beta.5.tgz", - "integrity": "sha512-q6PZsp8uFdQOt2CqhoNk040Ods0b8/uRC+lyzxCkNAeeRv/kXX/O/9PmwaVnpJAhRjrN5Fqx037dc0mGTAg+iw==", + "version": "8.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-8.0.0-beta.7.tgz", + "integrity": "sha512-ey4VYfAkkIMtYHmsJBoElaJn6BuKC/7/eX75ZLd7fVCOnGzsTs5m1FxlrB6cxSbN8EfR03qarbvNkmjHKUuWOw==", "requires": { "tslib": "^1.9.0" } @@ -675,9 +753,9 @@ } }, "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz", + "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==", "dev": true, "requires": { "regenerator-runtime": "^0.12.0" @@ -1017,33 +1095,35 @@ } }, "@compodoc/compodoc": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.8.tgz", - "integrity": "sha512-MuTE/KyNfRxJwLC3Ih2WXsMgd3YWFsViGxFbnrMigNYSq6/syUYVZgoj0E/Hir1au6k5jshKX1Vg7S61wqCaWg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.9.tgz", + "integrity": "sha512-LFdbF/fO9qOGgdrCHg4irMBrl3sD8ySIlL6XabFAz8QbUdf2+77xOVnZJ/PYdcuI8h8vXo4UGS7MFg0pDR2jsw==", "dev": true, "requires": { "@compodoc/ngd-transformer": "^2.0.0", "chalk": "^2.4.2", "cheerio": "^1.0.0-rc.2", - "chokidar": "^2.1.0", + "chokidar": "^2.1.2", "colors": "^1.3.3", - "commander": "2.19.0", - "cosmiconfig": "^5.0.7", + "commander": "^2.19.0", + "cosmiconfig": "^5.1.0", "fancy-log": "^1.3.3", "findit2": "^2.2.3", "fs-extra": "^7.0.1", "glob": "^7.1.3", - "handlebars": "4.0.10", + "handlebars": "^4.1.0", "html-entities": "^1.2.1", - "i18next": "^14.1.1", + "i18next": "^15.0.4", "inside": "^1.0.0", "json5": "^2.1.0", - "live-server": "1.2.1", + "live-server": "^1.2.1", "lodash": "^4.17.11", - "lunr": "2.3.5", - "marked": "^0.4.0", + "lunr": "^2.3.6", + "marked": "^0.6.1", + "minimist": "^1.2.0", "opencollective": "^1.0.3", "os-name": "^3.0.0", + "pdfmake": "^0.1.53", "semver": "^5.6.0", "traverse": "^0.6.6", "ts-simple-ast": "12.4.0", @@ -1515,145 +1595,6 @@ "@fortawesome/fontawesome-common-types": "^0.2.15" } }, - "@iamstarkov/listr-update-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", - "integrity": "sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - } - } - }, "@marionebl/sander": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@marionebl/sander/-/sander-0.6.1.tgz", @@ -1830,18 +1771,31 @@ "dev": true }, "@ngtools/webpack": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.3.tgz", - "integrity": "sha512-G/1P00XHWVrKT3qoSyy7yAPT5/fuja84YifcGg/2SwmNNo4hTXxWhqec0/uHwgQr6nYhGDyzwwXYeKKyQkcfgw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.5.tgz", + "integrity": "sha512-KqJ4ZR8XicN+ElrSNiNPidTM134Z23F7ib0Rl8Ny3PDHkAYIBIxEnQDgZ2mazKRUVMlStUnjmzQIQj7/qZGLaw==", "dev": true, "requires": { - "@angular-devkit/core": "7.3.3", + "@angular-devkit/core": "7.3.5", "enhanced-resolve": "4.1.0", "rxjs": "6.3.3", "tree-kill": "1.2.1", "webpack-sources": "1.3.0" }, "dependencies": { + "@angular-devkit/core": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz", + "integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -1854,17 +1808,17 @@ } }, "@ngx-formly/core": { - "version": "5.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-5.0.0-rc.6.tgz", - "integrity": "sha512-JvMBmeEidDzFqjqtafpl+PvBsKS+nYj6d9lSNZI8k1doRNHWPYiiSV/smDgR/8IeLqWcoGu818UwRfmYh5yjMw==", + "version": "5.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-5.0.0-rc.10.tgz", + "integrity": "sha512-0lLMp0EbTgBwQpIMiZ0TFgs1oU75LeqN88r6I6f1W4Pt/hcD5yXVT6LdMHZJALsdgvNjghFCWYv83sON9Qwn2g==", "requires": { "tslib": "^1.7.1" } }, "@ngx-formly/material": { - "version": "5.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-5.0.0-rc.6.tgz", - "integrity": "sha512-GSVSNgcHdBIGppR/EzVp0AzbOWudM+AuIaq+/86RQ6H5lV85w8k/V7YAWdMPI9lF86e6UQZYelkRN+DNykEmXw==", + "version": "5.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-5.0.0-rc.10.tgz", + "integrity": "sha512-DyS/T9wUvqye6bk9w3ExnGXOktRMT87ge2upiOpAwUN9wqL2ZvucycYj6cxF0YMsoVi5f1XUbTiK919lPx9Ocg==", "requires": { "tslib": "^1.9.0" } @@ -1993,25 +1947,25 @@ } }, "@ngxs/devtools-plugin": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.3.4.tgz", - "integrity": "sha512-gwVbK+utZlikIJ1wOC8WuOqAjB3t55J4LYiN9VwSxntvnXEAe+J2PQrnvK0e/C9df8HAbytGK2Jbisg00QO/ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.4.1.tgz", + "integrity": "sha512-VrpmYUnUVizarU31jffRRiKnFUPWV6/GQe6LcyHzhrzSKkYr63gyBAQMLFK7ZhSfoKpLok1vD1F+TfpG/r32/w==", "requires": { "tslib": "^1.9.0" } }, "@ngxs/form-plugin": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@ngxs/form-plugin/-/form-plugin-3.3.4.tgz", - "integrity": "sha512-mRE51CwjI/Eul/aPU803IVsz9oq8LwJtUGB7RStAoMwvpjG5Y1QEwjpBK7IXGx9iPTCBOkITdESaJNvNVzzv8w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ngxs/form-plugin/-/form-plugin-3.4.1.tgz", + "integrity": "sha512-+BFy1FTchs6zseUJMnlZvpNrFrmD5vzswBazU28G9VKdOuFrTQmuXRFmK5Nr4fOdDnZJhOeJ6/5xvPuRN3kmfg==", "requires": { "tslib": "^1.9.0" } }, "@ngxs/router-plugin": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@ngxs/router-plugin/-/router-plugin-3.3.4.tgz", - "integrity": "sha512-6yWteZz7oYRbTKrM+WWRbbCZAxW87Sd17WydUSHNVxZM1Yn5w2qVH16CCKO2nG2javA85Fk4Qw3VCbN8e33TTA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ngxs/router-plugin/-/router-plugin-3.4.1.tgz", + "integrity": "sha512-xmxZyx++5meikwph0oXyFKPp8mHDHBKdels89sykSM4TIRJeqkOcy6m22V5pOPAG2oWDUh+yjSSmAMwGLRaScA==", "requires": { "tslib": "^1.9.0" } @@ -2036,17 +1990,17 @@ } }, "@ngxs/storage-plugin": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@ngxs/storage-plugin/-/storage-plugin-3.3.4.tgz", - "integrity": "sha512-W2xSO3wv0KfadviP2JsA7BIzjqX3POnxn2enSWUa69s01pZ7gw92+lg4Gf1T6Whv9xK5whUn403f6zsDYGiuFA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ngxs/storage-plugin/-/storage-plugin-3.4.1.tgz", + "integrity": "sha512-l/Bxc12xgylTx5oJ+9cYjzNw3avJxvW1GYejFHWxb5dmPDNjNY67ODmlT8eA2bBosnXZCTqlA6vtcsXmOuXcSQ==", "requires": { "tslib": "^1.9.0" } }, "@ngxs/store": { - "version": "3.3.4-dev.master-0ac2fd9", - "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.3.4-dev.master-0ac2fd9.tgz", - "integrity": "sha512-KGJwZpt6bbV/YzxIkPltoFsMMQwdF4SogoJDSn2KOINww6LTq6vBNA35Sz5P8qL4JdU7jtgU/djyW817sms8nA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.4.1.tgz", + "integrity": "sha512-H+MRYC+NDhx0ysscqm8bOPYEdzeo94Oj3B3qCVTBXUsPHWyYUU565PdNcXqf1JxptS9gZBUfNnGxWzhREjWG3Q==", "requires": { "tslib": "^1.9.0" } @@ -2058,9 +2012,9 @@ "dev": true }, "@nrwl/builders": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@nrwl/builders/-/builders-7.6.0.tgz", - "integrity": "sha512-xi8TYyHvGS3bcBM26x00dW7eOCajXp3OQ8vkl1Wal6Ecn2+EUJntAfqxeCY71DPi5gYgi1pBS6WAF1lWy22icA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@nrwl/builders/-/builders-7.6.2.tgz", + "integrity": "sha512-1+DZP2qv1GeIJmrKqQnJ47Aga1uyw2aICEJLivaTJY7XmEnzxPBJoYBSJTi2Ti7fRU3zuY4cDr4Xvga/VASkUw==", "dev": true, "requires": { "@angular-devkit/architect": "~0.13.1", @@ -2109,14 +2063,14 @@ } }, "@nrwl/nx": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@nrwl/nx/-/nx-7.6.0.tgz", - "integrity": "sha512-oJjBCzo+2P9oHUJzdT7uVUmVqDDq1KkilwfeD+l81xavvTRp03gmOWrvosoirC0I8BX2Qzf5e7vw8JLiQD9X3w==" + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@nrwl/nx/-/nx-7.6.2.tgz", + "integrity": "sha512-bZE45JwEJ/qSUNnARZ9Mng3gkWC7wamGfm/Kmu1JefLwS9voDeaXzOYyEmMyc9GqAc8+s23dRNrdyhYlgR5Dmg==" }, "@nrwl/schematics": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@nrwl/schematics/-/schematics-7.6.0.tgz", - "integrity": "sha512-rt+YzuNpmVGIt5agPM5Z61cMaSLRmEcpiI4HdVL8eOBw7HicVk2N894rHbQdU3/LHZUt592zq8Z7jOFUtbV3JQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@nrwl/schematics/-/schematics-7.6.2.tgz", + "integrity": "sha512-b5oAoivGbcI2BwwTrTebFs/5ST7KHtnGQXj6uRoKvcdH41rfRDC+8Es2zo2qfmHAdb6SMY8OM9cwukOjZIJlyw==", "dev": true, "requires": { "@types/yargs": "^11.0.0", @@ -2282,9 +2236,9 @@ } }, "@octokit/request": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-2.3.0.tgz", - "integrity": "sha512-5YRqYNZOAaL7+nt7w3Scp6Sz4P2g7wKFP9npx1xdExMomk8/M/ICXVLYVam2wzxeY0cIc6wcKpjC5KI4jiNbGw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-2.4.0.tgz", + "integrity": "sha512-Bm2P0duVRUeKhyepNyFg5GX+yhCK71fqdtpsw5Rz+PQPjSha8HYwPMF5QfpzpD8b6/Xl3xhTgu3V90W362gZ1A==", "dev": true, "requires": { "@octokit/endpoint": "^3.1.1", @@ -2294,12 +2248,12 @@ } }, "@octokit/rest": { - "version": "16.16.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.16.0.tgz", - "integrity": "sha512-Q6L5OwQJrdJ188gLVmUHLKNXBoeCU0DynKPYW8iZQQoGNGws2hkP/CePVNlzzDgmjuv7o8dCrJgecvDcIHccTA==", + "version": "16.16.3", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.16.3.tgz", + "integrity": "sha512-8v5xyqXZwQbQ1WsTLU3G25nAlcKYEgIXzDeqLgTFpbzzJXcey0C8Mcs/LZiAgU8dDINZtO2dAPgd1cVKgK9DQw==", "dev": true, "requires": { - "@octokit/request": "2.3.0", + "@octokit/request": "2.4.0", "before-after-hook": "^1.2.0", "btoa-lite": "^1.0.0", "lodash.get": "^4.4.2", @@ -2329,15 +2283,44 @@ } }, "@schematics/angular": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.3.tgz", - "integrity": "sha512-HbH8vajYPka0xGcFAN5IUBx8n8SFMQLFb9di2dJCOBaEakbKVkk8qtOpil54oFQbx7DFCvutq/p0u42JfEbuMQ==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.5.tgz", + "integrity": "sha512-fKNZccf1l2OcDwtDupYj54N/YuiMLCWeaXNxcJNUYvGnBtzxQJ4P2LtSCjB4HDvYCtseQM7iyc7D1Xrr5gI8nw==", "requires": { - "@angular-devkit/core": "7.3.3", - "@angular-devkit/schematics": "7.3.3", + "@angular-devkit/core": "7.3.5", + "@angular-devkit/schematics": "7.3.5", "typescript": "3.2.4" }, "dependencies": { + "@angular-devkit/core": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz", + "integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==", + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "@angular-devkit/schematics": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.5.tgz", + "integrity": "sha512-BFCCkwRMBC4aFlngaloi1avCTgGrl1MFc/0Av2sCpBh/fdm1FqSVzmOiTfu93dehRVVL/bTrA2qj+xpNsXCxzA==", + "requires": { + "@angular-devkit/core": "7.3.5", + "rxjs": "6.3.3" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + }, "typescript": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", @@ -2346,42 +2329,54 @@ } }, "@schematics/update": { - "version": "0.14.0-beta.2", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.14.0-beta.2.tgz", - "integrity": "sha512-aYgkpLbhFFso7sZSkEWi5Uj7EHvLxiX9RtC1HNdrFeHvpW4UVgs1zHX/d9Lo4jn5u/xHQ0gGoDjSB21nkRVmAQ==", + "version": "0.14.0-beta.5", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.14.0-beta.5.tgz", + "integrity": "sha512-qqo3kJBmjlXl8HI4vkn+QKjOuWIv3QN3itlkhubrq9r8RRRd83+vgWD2zvitKbwy0wHqU70+n6e2IZwRiydAVg==", "dev": true, "requires": { - "@angular-devkit/core": "8.0.0-beta.2", - "@angular-devkit/schematics": "8.0.0-beta.2", + "@angular-devkit/core": "8.0.0-beta.5", + "@angular-devkit/schematics": "8.0.0-beta.5", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "pacote": "9.5.0", - "rxjs": "6.3.3", + "rxjs": "6.4.0", "semver": "5.6.0", "semver-intersect": "1.4.0" }, "dependencies": { "@angular-devkit/core": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.0-beta.2.tgz", - "integrity": "sha512-M8HNPLlmQ4zANIjEN4d7Ua+2qXtWvpSnXJO1bEefqD+usiqBJiaVj8JtINVVfkLuRW97dWibz3JsdKxyed6iaw==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.0.0-beta.5.tgz", + "integrity": "sha512-ob5HpUusC6Q/TsC1gGgeBvG1JZ53SYdCKBDPcLgpOAg1hAXKrbmmUGe4Y+UQtRSVYPEKceXv3L0cmqcjelBHXQ==", "dev": true, "requires": { - "ajv": "6.9.1", + "ajv": "6.10.0", "chokidar": "2.1.2", "fast-json-stable-stringify": "2.0.0", - "rxjs": "6.3.3", + "rxjs": "6.4.0", "source-map": "0.7.3" } }, "@angular-devkit/schematics": { - "version": "8.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.0.0-beta.2.tgz", - "integrity": "sha512-0CJb88akw1ozUwHHSWh79aa4sHmE9ZqFawzYKHKysR34LHJB83iER1ijyEure4gqGveptF9ufnPiynFtsZO8hg==", + "version": "8.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.0.0-beta.5.tgz", + "integrity": "sha512-4WQy5OeiLwGSswsAzdGS3yhfjlHLeRLHE60+I+EeYmdipggjj04WBohNduzypb1LZzqfKctduZdLv0od3N3Nkw==", "dev": true, "requires": { - "@angular-devkit/core": "8.0.0-beta.2", - "rxjs": "6.3.3" + "@angular-devkit/core": "8.0.0-beta.5", + "rxjs": "6.4.0" + } + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "chokidar": { @@ -2409,15 +2404,6 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } } } }, @@ -3121,9 +3107,9 @@ "dev": true }, "@types/babel-types": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.5.tgz", - "integrity": "sha512-0t0R7fKAXT/P++S98djRkXbL9Sxd9NNtfNg3BNw2EQOjVIkiMBdmO55N2Tp3wGK3mylmM7Vck9h5tEoSuSUabA==" + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.6.tgz", + "integrity": "sha512-8zYZyy2kgwBXdz2j8Ix7LOghGiZbOiHf6vqmmBX1r76FdAzVNv7cODyJTEglUWiOdRnXh0s/o58neUwv5vaitQ==" }, "@types/babylon": { "version": "6.16.5", @@ -3306,9 +3292,9 @@ "dev": true }, "@types/node": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz", - "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==" + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", + "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" }, "@types/nodemailer": { "version": "4.6.6", @@ -3407,9 +3393,9 @@ "dev": true }, "@types/superagent": { - "version": "3.8.6", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.6.tgz", - "integrity": "sha512-YQjdsk27MLb6uyXjjywGyYeuqavwV3CirHt6btBz00HkKJyowdB8gjjB1zIZxrOybDRqO8FLjTZeEtmtC2hqxA==", + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", "dev": true, "requires": { "@types/cookiejar": "*", @@ -3785,9 +3771,9 @@ } }, "aggregate-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-2.0.0.tgz", - "integrity": "sha512-xA1VQPApQdDehIIpS3gBFkMGDRb9pDYwZPVUOoX8A0lU3GB0mjiACqsa9ByBurU53erhjamf5I4VNRitCfXhjg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-2.1.0.tgz", + "integrity": "sha512-rIZJqC4XACGWwmPpi18IhDjIzXTJ93KQwYHXuyMCa0Ak9mtzLIbykuei+0i5EnGDy6ts8JVnSyRnZc2cVIMvVg==", "dev": true, "requires": { "clean-stack": "^2.0.0", @@ -4233,6 +4219,65 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "ast-transform": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", + "integrity": "sha1-dJRAWIh9goPhidlUYAlHvJj+AGI=", + "dev": true, + "requires": { + "escodegen": "~1.2.0", + "esprima": "~1.0.4", + "through": "~2.3.4" + }, + "dependencies": { + "escodegen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", + "dev": true, + "requires": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.30" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", + "dev": true + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", + "dev": true + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "ast-types": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", + "integrity": "sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk=", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -4737,9 +4782,9 @@ "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==" }, "bindings": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.4.0.tgz", - "integrity": "sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "requires": { "file-uri-to-path": "1.0.0" } @@ -4824,9 +4869,9 @@ "dev": true }, "bottleneck": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.16.2.tgz", - "integrity": "sha512-671wkIKV9K3yO/nfGw8tB4ihJazmZyd6DYEWxF1fuE7gzobk4w4atepHuYXoUHk78xLknjAGko8dXRcFO7iCoA==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.17.0.tgz", + "integrity": "sha512-0WpG/tVEBkhl9LLAFcjMTWhzQvLCYTD4wNlvtf+BZ9Q9xkMVpP5TbGPKkj2sADZtC/GQqgUMF2ij/7nD3abFLg==", "dev": true }, "boxen": { @@ -4919,12 +4964,33 @@ } } }, + "brfs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", + "integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==", + "dev": true, + "requires": { + "quote-stream": "^1.0.1", + "resolve": "^1.1.5", + "static-module": "^2.2.0", + "through2": "^2.0.0" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "brotli": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", + "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", + "dev": true, + "requires": { + "base64-js": "^1.1.2" + } + }, "browser-process-hrtime": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", @@ -5383,6 +5449,17 @@ "safe-buffer": "^5.1.2" } }, + "browserify-optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", + "integrity": "sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk=", + "dev": true, + "requires": { + "ast-transform": "0.0.0", + "ast-types": "^0.7.0", + "browser-resolve": "^1.8.1" + } + }, "browserify-rsa": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", @@ -5418,14 +5495,14 @@ } }, "browserslist": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", - "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.2.tgz", + "integrity": "sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000929", - "electron-to-chromium": "^1.3.103", - "node-releases": "^1.1.3" + "caniuse-lite": "^1.0.30000939", + "electron-to-chromium": "^1.3.113", + "node-releases": "^1.1.8" } }, "bs-logger": { @@ -5493,6 +5570,12 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", + "dev": true + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -5767,9 +5850,9 @@ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { - "version": "1.0.30000938", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000938.tgz", - "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", + "version": "1.0.30000939", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", + "integrity": "sha512-oXB23ImDJOgQpGjRv1tCtzAvJr4/OvrHi5SO2vUgB0g0xpdZZoA/BxfImiWfdwoYdUTtQrPsXsvYU/dmCSM8gg==", "dev": true }, "canonical-path": { @@ -6693,15 +6776,6 @@ "through2": "^2.0.0" }, "dependencies": { - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -6719,25 +6793,6 @@ "quick-lru": "^1.0.0" } }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, - "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - } - }, "indent-string": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", @@ -6826,12 +6881,6 @@ "strip-indent": "^2.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6849,17 +6898,6 @@ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", "dev": true - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - } } } }, @@ -7204,6 +7242,12 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=", + "dev": true + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -8232,6 +8276,15 @@ "integrity": "sha1-p2o+0YVb56ASu4rBbLgPPADcKPA=", "dev": true }, + "dfa": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.1.0.tgz", + "integrity": "sha1-0wIYvRDQMPpCHfPrvIIoVGOjF4E=", + "dev": true, + "requires": { + "babel-runtime": "^6.11.6" + } + }, "dicer": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", @@ -8774,9 +8827,9 @@ } }, "es5-ext": { - "version": "0.10.47", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.47.tgz", - "integrity": "sha512-/1TItLfj+TTfWoeRcDn/0FbGV6SNo4R+On2GGVucPU/j3BWnXE2Co8h8CTo4Tu34gFJtnmwS9xiScKs4EjZhdw==", + "version": "0.10.48", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.48.tgz", + "integrity": "sha512-CdRvPlX/24Mj5L4NVxTs4804sxiS2CjVprgCmrgoDkdmjdY4D+ySHa7K3jJf8R40dFg0tIm3z/dk326LrnuSGw==", "requires": { "es6-iterator": "~2.0.3", "es6-symbol": "~3.1.1", @@ -8837,9 +8890,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", + "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", "dev": true, "requires": { "esprima": "^3.1.3", @@ -8875,9 +8928,9 @@ } }, "esm": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.5.tgz", - "integrity": "sha512-rukU6Nd3agbHQCJWV4rrlZxqpbO3ix8qhUxK1BhKALGS2E465O0BFwgCOqJjNnYfO/I2MwpUBmPsW8DXoe8tcA==" + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.7.tgz", + "integrity": "sha512-zsyD5gO8CY9dpK3IrdC4WHtvtHGXEFOpYA4zB+6p+Kygf3vv/6kF3YMEQLOArwKPPNvKt8gjI8UYhQW8bXM/YQ==" }, "esprima": { "version": "4.0.1", @@ -9426,6 +9479,32 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "falafel": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz", + "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=", + "dev": true, + "requires": { + "acorn": "^5.0.0", + "foreach": "^2.0.5", + "isarray": "0.0.1", + "object-keys": "^1.0.6" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -9770,6 +9849,33 @@ } } }, + "fontkit": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.7.8.tgz", + "integrity": "sha512-4bo/Sp+Ob/cE5f2aCre/N42mHe6hcIWUYmTRgEqLmJyPRX0m0KBl1jflMM70Vh7qZRoh97BcS3JQGrRvK1Ga7Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.11.6", + "brfs": "^1.4.0", + "brotli": "^1.2.0", + "browserify-optional": "^1.0.0", + "clone": "^1.0.1", + "deep-equal": "^1.0.0", + "dfa": "^1.0.0", + "restructure": "^0.5.3", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.0.0", + "unicode-trie": "^0.3.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -9784,6 +9890,12 @@ "for-in": "^1.0.1" } }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -10964,24 +11076,48 @@ "dev": true }, "handlebars": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { - "async": "^1.4.0", + "async": "^2.5.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, + "optional": true, "requires": { - "amdefine": ">=0.0.4" + "commander": "~2.17.1", + "source-map": "~0.6.1" } } } @@ -11167,9 +11303,9 @@ "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" }, "highlight.js": { - "version": "9.14.2", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.14.2.tgz", - "integrity": "sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA==" + "version": "9.15.6", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.6.tgz", + "integrity": "sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -11271,9 +11407,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -11615,10 +11751,13 @@ } }, "i18next": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-14.1.1.tgz", - "integrity": "sha512-HItn9RHLyrDqe6pw6qXMYHGPHNc3y1FZndJfBlD6k4sRS0FAlYLvqCDVIWFc1XultBgsv348TtvL/lleG6JgBg==", - "dev": true + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-15.0.4.tgz", + "integrity": "sha512-msRzIP/9Vo6wMbbkFRoosVu5X55vARjfhX3H9Z2avRoDAjIBNsXfpfCVF3hbP/2aMQDNEds3NUuDxE4XhcAMnA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.3.1" + } }, "iconv-lite": { "version": "0.4.23", @@ -11673,9 +11812,9 @@ "optional": true }, "immer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-2.0.0.tgz", - "integrity": "sha512-qwwvbGbidU0P5SjO4s1wZ9hjNj8fQ908UVtKDSAYgxuDiY1MFylCvsQJSr/fUUo3aeHdxZapdpzPi3vpwTUXUQ==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-2.1.1.tgz", + "integrity": "sha512-+S4ktJM7ugEy1aIfpsjTVf7ZLMMuwz8QqSxpJRZaCM+0ujfeU4kWcj57P02qQ/KRGMw7DwUS4lCKNNROHasXkw==" }, "immutable": { "version": "3.8.2", @@ -13711,9 +13850,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -13775,9 +13914,9 @@ }, "dependencies": { "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -13959,9 +14098,9 @@ } }, "keycloak-js": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-4.8.3.tgz", - "integrity": "sha512-TXoZdoOYu2ScYs58L95/xSYjsTto9KRvZ+vt6mv4Dyf4pYhYZSgwMPnmi128qj/z8sm4mL1Z8nncR6XdWgNKMQ==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-5.0.0.tgz", + "integrity": "sha512-zJRgb9E1IV3GLXTMow2cpvRK1PEx9shVDx96uac7zO+uTG2ptLvrYxs1umemjGqDPmy5LJiaUgF30fc9MS3ykQ==" }, "keyv": { "version": "3.0.0", @@ -13990,9 +14129,9 @@ "dev": true }, "kubernetes-client": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/kubernetes-client/-/kubernetes-client-6.9.0.tgz", - "integrity": "sha512-z3kuWACH44yVXxOL7bm0bnqCNtnXFvxBZ4JpYRIsRkQHw6eKYw+a1fb2Dzr2JyD572zZ5azxPK1/5SCfhtSgtA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/kubernetes-client/-/kubernetes-client-6.10.0.tgz", + "integrity": "sha512-PFUvbRTmVUScv0ZNizBy0AV8Yi3ViFJgGwUkZ1gpJ3XsHJ8pHNOr9qdl3oJLOt/70ldC9M1zcbjBgmgSZEWqWQ==", "dev": true, "requires": { "deepmerge": "^3.0.0", @@ -14134,13 +14273,31 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz", "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==" }, + "linebreak": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-0.3.0.tgz", + "integrity": "sha1-BSZICmLAW9Z58+nZmDDgnGp9DtY=", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "brfs": "^1.3.0", + "unicode-trie": "^0.3.0" + }, + "dependencies": { + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + } + } + }, "lint-staged": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.4.tgz", - "integrity": "sha512-oFbbhB/VzN8B3i/sIdb9gMfngGArI6jIfxSn+WPdQb2Ni3GJeS6T4j5VriSbQfxfMuYoQlMHOoFt+lfcWV0HfA==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.5.tgz", + "integrity": "sha512-e5ZavfnSLcBJE1BTzRTqw6ly8OkqVyO3GL2M6teSmTBYQ/2BuueD5GIt2RPsP31u/vjKdexUyDCxSyK75q4BDA==", "dev": true, "requires": { - "@iamstarkov/listr-update-renderer": "0.4.1", "chalk": "^2.3.1", "commander": "^2.14.1", "cosmiconfig": "^5.0.2", @@ -14153,6 +14310,7 @@ "is-glob": "^4.0.0", "is-windows": "^1.0.2", "listr": "^0.14.2", + "listr-update-renderer": "^0.5.0", "lodash": "^4.17.11", "log-symbols": "^2.2.0", "micromatch": "^3.1.8", @@ -14997,12 +15155,6 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, - "lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", - "dev": true - }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -15026,18 +15178,6 @@ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, - "lodash.fill": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/lodash.fill/-/lodash.fill-3.4.0.tgz", - "integrity": "sha1-o8dK5kDQU63w3CB5+HIHiOi/74U=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -15049,12 +15189,6 @@ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "lodash.intersection": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.intersection/-/lodash.intersection-4.4.0.tgz", - "integrity": "sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU=", - "dev": true - }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -15098,41 +15232,17 @@ "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", "dev": true }, - "lodash.merge": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", - "dev": true - }, "lodash.mergewith": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, - "lodash.omit": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=", - "dev": true - }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, - "lodash.partialright": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", - "integrity": "sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -15317,9 +15427,9 @@ } }, "lunr": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.5.tgz", - "integrity": "sha512-EtnfmHsHJTr3u24sito9JctSxej5Ds0SgUD2Lm+qRHyLgM7BGesFlW14eNh1mil0fV5Muh8gf3dBBXzADlUlzQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.6.tgz", + "integrity": "sha512-swStvEyDqQ85MGpABCMBclZcLI/pBIlu8FFDtmX197+oEgKloJ67QnB+Tidh0340HmLMs39c4GrkPY3cmkXp6Q==", "dev": true }, "macos-release": { @@ -15516,9 +15626,9 @@ } }, "marked": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz", - "integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz", + "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA==", "dev": true }, "marked-terminal": { @@ -15643,11 +15753,101 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, + "merge-deep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", + "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "dependencies": { + "clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", + "dev": true, + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + } + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "dev": true, + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", + "dev": true + } + } + } + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -16228,9 +16428,9 @@ } }, "ngx-quicklink": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.0.7.tgz", - "integrity": "sha512-BvGhC3wQgJm0ZyrtWFsKzJvnPzVFBbln+O8WUdoMvWZyDKuoX74bJtCA8/t5JoVpeHGDhkzSsR0/n7zxmXJNjA==" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.0.8.tgz", + "integrity": "sha512-kGg0OWyEoUOqd0awBkl3njiSZcbyGzEGKtf0nefr/Lj0t9R6t1HAOgPEWZ+ebZG8KnRnCXtGqpqNgD/bgKR4jQ==" }, "nice-try": { "version": "1.0.5", @@ -16327,32 +16527,23 @@ "dev": true }, "node-jose": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.1.tgz", - "integrity": "sha512-GuMQE1UNZGoOX2mPoQ4pYYkynSrDNiq7CzY1YKoJBmFYBZMsAVoAmzpAKBz2uBLNnjet7A4idGXdE3Lck1gjqQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.2.tgz", + "integrity": "sha512-SotC7+9MGPSDcMlrLFcv09nv33Sw/WPT7qgZOLC0v6nY8E4n7dt7orGO13rGtb3kK163VEwJO06owkXY7LWjrQ==", "dev": true, "requires": { - "base64url": "^3.0.0", - "es6-promise": "^4.2.5", - "lodash.assign": "^4.2.0", - "lodash.clone": "^4.5.0", - "lodash.fill": "^3.4.0", - "lodash.flatten": "^4.4.0", - "lodash.intersection": "^4.4.0", - "lodash.merge": "^4.6.1", - "lodash.omit": "^4.5.0", - "lodash.partialright": "^4.2.1", - "lodash.pick": "^4.4.0", - "lodash.uniq": "^4.5.0", + "base64url": "^3.0.1", + "es6-promise": "^4.2.6", + "lodash": "^4.17.11", "long": "^4.0.0", - "node-forge": "^0.8.0", + "node-forge": "^0.8.1", "uuid": "^3.3.2" }, "dependencies": { "node-forge": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.0.tgz", - "integrity": "sha512-DVrvVeXwnSSX0Bgi9jy8p4IXQKLhGRQltG+UTR3Oci3Wb/zIROMoxw9im/K5s6KnNMheSWgG8K4qz8Njccdj3g==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.1.tgz", + "integrity": "sha512-C/42HVb5eRtnDgRKOFx4bJH6LwbGQwbEHOC/trQwQSR6xWAUR/jlWoUJnxOmFTvdmDIZtjl2VH4dl3VpQuIz5g==", "dev": true } } @@ -16609,9 +16800,9 @@ "dev": true }, "normalize-url": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.1.0.tgz", - "integrity": "sha512-X781mkWeK6PDMAZJfGn/wnwil4dV6pIdF9euiNqtA89uJvZuNDJO2YyJxiwpPhTQcF5pYUU1v+kcOxzYV6rZlA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.2.0.tgz", + "integrity": "sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg==", "dev": true }, "npm": { @@ -19910,9 +20101,9 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", - "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", "dev": true }, "oauth-sign": { @@ -19963,6 +20154,12 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==" }, + "object-inspect": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", + "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==", + "dev": true + }, "object-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", @@ -20049,9 +20246,9 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "dev": true }, "once": { @@ -20599,9 +20796,9 @@ } }, "pako": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", - "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "dev": true }, "parallel-transform": { @@ -20861,6 +21058,41 @@ "sha.js": "^2.4.8" } }, + "pdfkit": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.9.0.tgz", + "integrity": "sha512-j/tJvvoEM7qMiOfodNkz6XRNBvAhVohWP1mUvgg+Xy8X8S3aMBpVGXJGZWNCoCUH2ywLidZSbHyn5mJaqv9umw==", + "dev": true, + "requires": { + "crypto-js": "^3.1.9-1", + "fontkit": "^1.0.0", + "linebreak": "^0.3.0", + "png-js": ">=0.1.0", + "saslprep": "1.0.1" + } + }, + "pdfmake": { + "version": "0.1.53", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.1.53.tgz", + "integrity": "sha512-VkS4QEs2dSA0CVGscUZOnTXsSegfcUHRHFxdDs11qkJvyfCzMmeI7sWCDYMjrjXzYoPamxeXEgjY7LOmw3zT7Q==", + "dev": true, + "requires": { + "iconv-lite": "^0.4.24", + "linebreak": "^0.3.0", + "pdfkit": "^0.9.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -21029,6 +21261,12 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "png-js": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-0.1.1.tgz", + "integrity": "sha1-HMfCEjA6yr50Jj7DrHgAlYAkLZM=", + "dev": true + }, "portfinder": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", @@ -21160,9 +21398,9 @@ "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" }, "postgres-interval": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.2.tgz", - "integrity": "sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { "xtend": "^4.0.0" } @@ -21588,6 +21826,25 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "quote-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", + "dev": true, + "requires": { + "buffer-equal": "0.0.1", + "minimist": "^1.1.3", + "through2": "^2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "ramda": { "version": "0.24.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", @@ -22090,6 +22347,15 @@ "signal-exit": "^3.0.2" } }, + "restructure": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz", + "integrity": "sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg=", + "dev": true, + "requires": { + "browserify-optional": "^1.0.0" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -22145,14 +22411,14 @@ } }, "rollup-plugin-commonjs": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz", - "integrity": "sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.1.tgz", + "integrity": "sha512-X0A/Cp/t+zbONFinBhiTZrfuUaVwRIp4xsbKq/2ohA2CDULa/7ONSJTelqxon+Vds2R2t2qJTqJQucKUC8GKkw==", "dev": true, "requires": { "estree-walker": "^0.5.2", "magic-string": "^0.25.1", - "resolve": "^1.8.1", + "resolve": "^1.10.0", "rollup-pluginutils": "^2.3.3" } }, @@ -22166,14 +22432,14 @@ } }, "rollup-plugin-node-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz", - "integrity": "sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.1.tgz", + "integrity": "sha512-fSS7YDuCe0gYqKsr5OvxMloeZYUSgN43Ypi1WeRZzQcWtHgFayV5tUSPYpxuaioIIWaBXl6NrVk0T2/sKwueLg==", "dev": true, "requires": { "builtin-modules": "^3.0.0", "is-module": "^1.0.0", - "resolve": "^1.8.1" + "resolve": "^1.10.0" } }, "rollup-plugin-sourcemaps": { @@ -22254,16 +22520,17 @@ } }, "rxjs-tslint-rules": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/rxjs-tslint-rules/-/rxjs-tslint-rules-4.17.0.tgz", - "integrity": "sha512-LDiLOSxB3+tFhXM4iXaB87izkmOPImByDjR08PsIp5NQfngx0hWW52l63+rJDBs4BFd0Ojwa+RLk09lFLz2JRA==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rxjs-tslint-rules/-/rxjs-tslint-rules-4.18.1.tgz", + "integrity": "sha512-NQO34MalznGrKE5Ow8l1KtXYjE7C7DemYFy3OIOHvDNgTEtIr77Pu0xxffJ4drHm1GPUTFoZDYFHbEadvzR7jw==", "dev": true, "requires": { "@phenomnomnominal/tsquery": "^3.0.0", "decamelize": "^2.0.0", "resolve": "^1.4.0", "tslib": "^1.8.0", - "tsutils": "^3.0.0" + "tsutils": "^3.0.0", + "tsutils-etc": "^1.0.0" }, "dependencies": { "decamelize": { @@ -22320,6 +22587,12 @@ } } }, + "saslprep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.1.tgz", + "integrity": "sha512-ntN6SbE3hRqd45PKKadRPgA+xHPWg5lPSj2JWJdJvjTwXDDfkPVtXWvP8jJojvnm+rAsZ2b299C5NwZqq818EA==", + "dev": true + }, "sass-graph": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", @@ -22653,12 +22926,6 @@ "path-exists": "^3.0.0" } }, - "marked": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.1.tgz", - "integrity": "sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA==", - "dev": true - }, "mem": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", @@ -22990,6 +23257,12 @@ } } }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, "sharp": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.21.3.tgz", @@ -23061,9 +23334,9 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "signale": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/signale/-/signale-1.3.0.tgz", - "integrity": "sha512-TyFhsQ9wZDYDfsPqWMyjCxsDoMwfpsT0130Mce7wDiVCSDdtWSg83dOqoj8aGpGCs3n1YPcam6sT1OFPuGT/OQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "requires": { "chalk": "^2.3.2", @@ -23595,9 +23868,9 @@ "dev": true }, "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -23688,6 +23961,15 @@ "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==", "dev": true }, + "static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dev": true, + "requires": { + "escodegen": "^1.8.1" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -23707,6 +23989,39 @@ } } }, + "static-module": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-2.2.5.tgz", + "integrity": "sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ==", + "dev": true, + "requires": { + "concat-stream": "~1.6.0", + "convert-source-map": "^1.5.1", + "duplexer2": "~0.1.4", + "escodegen": "~1.9.0", + "falafel": "^2.1.0", + "has": "^1.0.1", + "magic-string": "^0.22.4", + "merge-source-map": "1.0.4", + "object-inspect": "~1.4.0", + "quote-stream": "~1.0.2", + "readable-stream": "~2.3.3", + "shallow-copy": "~0.0.1", + "static-eval": "^2.0.0", + "through2": "~2.0.3" + }, + "dependencies": { + "magic-string": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "dev": true, + "requires": { + "vlq": "^0.2.2" + } + } + } + }, "stats-webpack-plugin": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.7.0.tgz", @@ -24076,12 +24391,12 @@ } }, "swagger-fluent": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/swagger-fluent/-/swagger-fluent-3.1.1.tgz", - "integrity": "sha512-6yBqpyJJZxKQRnN39wa9z8xHlin8zCcDZh7ndj7OMYpGvLhVadX2t6BdaUHt6mmOQwEJJAq71aMkE7aIlOAQSw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/swagger-fluent/-/swagger-fluent-3.1.2.tgz", + "integrity": "sha512-aTcbHTosk3FofPw3Vaosl9N8gNNdtjGpoGspKjAwRmmb4ocIqOfTKgPV4g04Fy4XLGiiW8S432vBaPazQxad7g==", "dev": true, "requires": { - "lodash.merge": "^4.6.1", + "merge-deep": "^3.0.2", "request": "^2.88.0" } }, @@ -24610,6 +24925,12 @@ "next-tick": "1" } }, + "tiny-inflate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.2.tgz", + "integrity": "sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c=", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -24986,9 +25307,9 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tslint": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.1.tgz", - "integrity": "sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", + "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -24999,6 +25320,7 @@ "glob": "^7.1.1", "js-yaml": "^3.7.0", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", @@ -25037,6 +25359,12 @@ "tslib": "^1.8.1" } }, + "tsutils-etc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tsutils-etc/-/tsutils-etc-1.0.1.tgz", + "integrity": "sha512-Yp6RQzPfxuZNjw/tHYdr+QRHtoPX2Al+9UmydyMidGqKt3Y13lJhryLUNPxYce5Y8KQ0Q6qn2MLJIxwL48GBWg==", + "dev": true + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -25080,9 +25408,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typeorm": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.13.tgz", - "integrity": "sha512-SfRHZt6un1rCsTtHpBSgG5kh2jWznzrBex1Dfxbnji0nlrrNQ+ET4uxIhq3herk0TE0ETx3R+UjU0MGv8DB/3g==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.14.tgz", + "integrity": "sha512-kPV8nX7g7hvFpuzdLKGxqkVRHEJe9AK6csgWJJPaCFU3NrLmUG6a2Q0FxY/Z0Qy/2gqt5VG3t3FyFpw360XSvg==", "requires": { "app-root-path": "^2.0.1", "buffer": "^5.1.0", @@ -25360,6 +25688,34 @@ "debug": "^2.2.0" } }, + "unicode-properties": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.1.0.tgz", + "integrity": "sha1-epbu9J91aC6mnSMV7smsQ//fAME=", + "dev": true, + "requires": { + "brfs": "^1.4.0", + "unicode-trie": "^0.3.0" + } + }, + "unicode-trie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", + "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", + "dev": true, + "requires": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + }, + "dependencies": { + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + } + } + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -25678,6 +26034,12 @@ "integrity": "sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==", "dev": true }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -25813,9 +26175,9 @@ }, "dependencies": { "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true }, "schema-utils": { @@ -25831,12 +26193,13 @@ } }, "webpack-bundle-analyzer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.4.tgz", - "integrity": "sha512-ggDUgtKuQki4vmc93Ej65GlYxeCUR/0THa7gA+iqAGC2FFAxO+r+RM9sAUa8HWdw4gJ3/NZHX/QUcVgRjdIsDg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.1.0.tgz", + "integrity": "sha512-nyDyWEs7C6DZlgvu1pR1zzJfIWSiGPbtaByZr8q+Fd2xp70FuM/8ngCJzj3Er1TYRLSFmp1F1OInbEm4DZH8NA==", "dev": true, "requires": { - "acorn": "^5.7.3", + "acorn": "^6.0.7", + "acorn-walk": "^6.1.1", "bfj": "^6.1.1", "chalk": "^2.4.1", "commander": "^2.18.0", @@ -25851,9 +26214,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -26653,6 +27016,23 @@ "property-expr": "^1.5.0", "synchronous-promise": "^2.0.5", "toposort": "^2.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + } } }, "zone.js": { diff --git a/package.api.json b/package.api.json index 395bbf1de..3033afaf0 100644 --- a/package.api.json +++ b/package.api.json @@ -23,7 +23,7 @@ "reflect-metadata": "^0.1.12", "rxjs": "^6.4.0", "sharp": "^0.21.3", - "typeorm": "^0.2.12", + "typeorm": "^0.2.14", "web-push": "^3.3.3" } } diff --git a/package.json b/package.json index 9b5e118d9..649b7538f 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,10 @@ "type": "git", "url": "https://github.com/xmlking/ngx-starter-kit.git" }, + "workspaces": [ + "libs/*", + "apps/*" + ], "scripts": { "ng": "ng", "start": "ng serve", @@ -97,19 +101,19 @@ }, "private": true, "dependencies": { - "@angular/animations": "^8.0.0-beta.5", + "@angular/animations": "^8.0.0-beta.7", "@angular/cdk": "^7.3.3", - "@angular/common": "^8.0.0-beta.5", - "@angular/compiler": "^8.0.0-beta.5", - "@angular/core": "^8.0.0-beta.5", + "@angular/common": "^8.0.0-beta.7", + "@angular/compiler": "^8.0.0-beta.7", + "@angular/core": "^8.0.0-beta.7", "@angular/flex-layout": "^7.0.0-beta.23", - "@angular/forms": "^8.0.0-beta.5", + "@angular/forms": "^8.0.0-beta.7", "@angular/material": "^7.3.3", - "@angular/platform-browser": "^8.0.0-beta.5", - "@angular/platform-browser-dynamic": "^8.0.0-beta.5", - "@angular/pwa": "^0.13.3", - "@angular/router": "^8.0.0-beta.5", - "@angular/service-worker": "^8.0.0-beta.5", + "@angular/platform-browser": "^8.0.0-beta.7", + "@angular/platform-browser-dynamic": "^8.0.0-beta.7", + "@angular/pwa": "^0.13.5", + "@angular/router": "^8.0.0-beta.7", + "@angular/service-worker": "^8.0.0-beta.7", "@fortawesome/angular-fontawesome": "^0.3.0", "@fortawesome/fontawesome-svg-core": "^1.2.0", "@fortawesome/free-brands-svg-icons": "^5.7.0", @@ -122,17 +126,17 @@ "@nestjs/swagger": "^2.5.0", "@nestjs/typeorm": "^5.3.0", "@nestjs/websockets": "^5.7.0", - "@ngx-formly/core": "^5.0.0-rc.6", - "@ngx-formly/material": "^5.0.0-rc.6", + "@ngx-formly/core": "^5.0.0-rc.10", + "@ngx-formly/material": "^5.0.0-rc.10", "@ngx-lite/in-viewport": "^0.1.3", "@ngx-lite/input-star-rating": "^0.2.5", "@ngx-lite/input-tag": "^0.2.8", "@ngxs-labs/immer-adapter": "^1.1.0", - "@ngxs/devtools-plugin": "^3.3.0", - "@ngxs/form-plugin": "^3.3.0", - "@ngxs/router-plugin": "^3.3.0", - "@ngxs/storage-plugin": "^3.3.0", - "@ngxs/store": "dev", + "@ngxs/devtools-plugin": "^3.4.0", + "@ngxs/form-plugin": "^3.4.0", + "@ngxs/router-plugin": "^3.4.0", + "@ngxs/storage-plugin": "^3.4.0", + "@ngxs/store": "^3.4.0", "@nrwl/nx": "^7.6.0", "@xmlking/angular-oauth2-oidc-all": "^5.2.1", "@xmlking/api-ai-javascript": "^2.0.0-beta.22", @@ -161,15 +165,15 @@ "formidable": "^1.2.1", "hammerjs": "^2.0.8", "helmet": "^3.15.0", - "immer": "^2.0.0", + "immer": "^2.1.0", "intersection-observer": "^0.5.1", - "keycloak-js": "^4.8.3", + "keycloak-js": "^5.0.0", "nest-router": "^1.0.0", "ngx-filepond": "^5.0.0", "ngx-page-scroll": "^6.0.0-beta.1", "ngx-page-scroll-core": "^6.0.0-beta.1", "ngx-perfect-scrollbar": "^7.2.0", - "ngx-quicklink": "0.0.7", + "ngx-quicklink": "^0.0.8", "nodemailer": "^5.1.0", "passport": "^0.4.0", "passport-jwt": "^4.0.0", @@ -180,17 +184,17 @@ "sharp": "^0.21.3", "smooth-scrollbar": "^8.3.1", "socket.io-client": "^2.2.0", - "typeorm": "^0.2.13", + "typeorm": "^0.2.14", "uuid": "^3.3.2", "web-push": "^3.3.3", "zone.js": "^0.8.29" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.13.2", - "@angular-devkit/build-ng-packagr": "^0.13.2", - "@angular/cli": "^8.0.0-beta.1", - "@angular/compiler-cli": "^8.0.0-beta.5", - "@angular/language-service": "^8.0.0-beta.5", + "@angular-devkit/build-angular": "^0.13.5", + "@angular-devkit/build-ng-packagr": "^0.13.5", + "@angular/cli": "^8.0.0-beta.5", + "@angular/compiler-cli": "^8.0.0-beta.7", + "@angular/language-service": "^8.0.0-beta.7", "@commitlint/cli": "^7.5.0", "@commitlint/config-conventional": "^7.5.0", "@compodoc/compodoc": "^1.1.0", @@ -198,8 +202,8 @@ "@nestjs/testing": "^5.7.2", "@ngx-formly/schematics": "5.0.0-beta.5", "@ngxs/schematics": "0.0.1-alpha.5", - "@nrwl/builders": "^7.6.0", - "@nrwl/schematics": "^7.6.0", + "@nrwl/builders": "^7.6.2", + "@nrwl/schematics": "^7.6.2", "@semantic-release/changelog": "^3.0.0", "@semantic-release/git": "^7.0.0", "@semantic-release/github": "^5.2.0", @@ -209,7 +213,7 @@ "@types/helmet": "^0.0.42", "@types/jest": "^23.3.0", "@types/jquery": "^3.3.29", - "@types/node": "^11.9.0", + "@types/node": "^11.10.0", "@types/nodemailer": "^4.6.0", "@types/passport": "1.0.0", "@types/passport-jwt": "^3.0.1", @@ -226,7 +230,7 @@ "husky": "^1.3.0", "jest": "^23.6.0", "jest-preset-angular": "^6.0.2", - "kubernetes-client": "^6.9.0", + "kubernetes-client": "^6.10.0", "lint-staged": "^8.1.0", "lite-server": "^2.4.0", "loaders.css": "^0.1.2", @@ -234,7 +238,7 @@ "nodemon": "^1.18.0", "prettier": "^1.16.0", "rimraf": "^2.6.2", - "rxjs-tslint-rules": "^4.17.0", + "rxjs-tslint-rules": "^4.18.0", "semantic-release": "^15.13.0", "supertest": "^3.4.0", "ts-jest": "^23.10.0", @@ -243,13 +247,9 @@ "tsconfig-paths": "^3.8.0", "tsickle": "^0.34.0", "tslib": "^1.9.3", - "tslint": "^5.12.0", + "tslint": "^5.13.0", "tslint-config-prettier": "^1.18.0", "typescript": "~3.1.6", - "webpack-bundle-analyzer": "^3.0.0" - }, - "workspaces": [ - "libs/*", - "apps/*" - ] + "webpack-bundle-analyzer": "^3.1.0" + } } diff --git a/stories/awesome.md b/stories/awesome.md index bf8c208c6..6e50cf1d8 100644 --- a/stories/awesome.md +++ b/stories/awesome.md @@ -60,9 +60,11 @@ A curated list of awesome Angular resources - WTF is monorepo? **Monorepo != Monolith** + > When you have all your codebase within the same repository it’s tempting to fall into the trap of creating a monolith (a gigantic application where all parts are intertwined with each other) so we need to ensure that modularity is a first class citizen within these repositories. > Each package has clearly defined boundaries and ownership. + - [Angular Enterprise Monorepo Patterns](https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book) - [Advantages of monorepos](https://danluu.com/monorepo/) - [Getting Started with Nx: The Nrwl Extensions for Angular](http://blog.ng-book.com/getting-started-with-nx-the-nrwl-extensions-for-angular/) @@ -71,6 +73,9 @@ A curated list of awesome Angular resources - [Nx monorepo with Ionic4](https://github.com/TeamHive/app-starter) - [Create Your First Custom Angular CLI Schematic with Nx](https://auth0.com/blog/create-custom-schematics-with-nx/) - [Why Angular Teams Fail at Code Sharing and How This Monorepo Approach Will Fix It](https://christianlydemann.com/why-angular-teams-fail-at-code-sharing-and-how-this-mono-repo-approach-will-fix-it/) + - [Harmony with Angular, Lerna and Yarn Workspaces](https://medium.com/@zachary.n.feldman/harmony-with-angular-lerna-and-yarn-workspaces-6a7394f08da) + - [NestJS mono-repo starter](https://github.com/BrunnerLivio/nestjs-monorepo-starter) + - [Sustainable Angular Architectures With Strategic Design And Monorepos - Part 1: Methodology](https://www.softwarearchitekt.at/post/2019/03/04/sustainable-angular-architectures-with-strategic-design-and-monorepos-part-1-methodology.aspx) - What are the guidelines to setup monorepo for enterprise size apps? @@ -204,6 +209,10 @@ semantic-release is a fully automated library/system for versioning, changelog g > Use[ng-packagr](https://github.com/dherges/ng-packagr) > Use[with nx](https://github.com/dherges/nx-packaged) +- How to lazy load modules ? + +> Use [Angular Loadable](https://medium.com/@zamamohammed/announcing-angular-loadable-ngx-loadable-2-2kb-4ef7e6321784) for non-routable modules + - How to implement security interceptors? > [Refer](https://medium.com/@ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8) diff --git a/stories/howto.md b/stories/howto.md index dc569a7db..7db4861a3 100644 --- a/stories/howto.md +++ b/stories/howto.md @@ -52,6 +52,8 @@ module.exports = { using travis CI/CD +[The way to fully automated releases in open source projects](https://medium.com/@kevinkreuzer/the-way-to-fully-automated-releases-in-open-source-projects-44c015f38fd6) + > Commits that have [ci skip] or [skip ci] anywhere in the commit messages are ignored by Travis CI. > [refer](http://dev.topheman.com/continuous-deployment-with-travis-ci/) @@ -276,7 +278,8 @@ NODE_DEBUG=request npm run api:start:dev 1. [Transform VSCode Into Best Angular Dev Environment](https://blog.feathersjs.com/design-patterns-for-modern-web-apis-1f046635215) - + +
@@ -286,7 +289,19 @@ NODE_DEBUG=request npm run api:start:dev 1. [Design patterns for modern web APIs](https://github.com/nrwl/angular-vscode) -
+ + + +
+ + +#### How to setup HMR with NGXS? + + + +1. [NGXS with HMR Plugin](https://medium.com/ngxs/ngxs-with-hmr-plugin-c2004bcf576d) + +
Build Error: No name was provided for external module 'date-fns/esm' in output.globals – guessing 'esm' Solution: Add umdModuleIds to `ng-package.json` diff --git a/stories/keycloak.md b/stories/keycloak.md index b84adb4ff..0030a5642 100644 --- a/stories/keycloak.md +++ b/stories/keycloak.md @@ -21,8 +21,8 @@ Pre-configured KeyCloak OpenID Connect server for testing. ```json auth: { - clientId: 'cockpit', - issuer: 'http://localhost:9080/auth/realms/ngx' + clientId: 'ngxapp', + issuer: 'http://localhost:8080/auth/realms/ngx', } ``` @@ -44,8 +44,8 @@ docker-compose exec keycloak sh ### Use -http://localhost:m080/ -> admin: admin +http://localhost:8080/ +> admin: admin123 ### Setup @@ -60,7 +60,7 @@ http://localhost:m080/ ```bash # Environment variable. change as per your server setup OIDC_ISSUER_URL=http://localhost:8080/auth/realms/ngx -OIDC_CLIENT=cockpit +OIDC_CLIENT=ngxapp USERNAME=sumo PASSWORD=demo