Skip to content

Commit

Permalink
feat(api): polish, refactored APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
xmlking committed Mar 6, 2019
1 parent 49c4c75 commit c7340b3
Show file tree
Hide file tree
Showing 100 changed files with 2,942 additions and 835 deletions.
16 changes: 16 additions & 0 deletions PLAYBOOK-NEST.md
Expand Up @@ -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
Expand Down
34 changes: 20 additions & 14 deletions PLAYBOOK.md
Expand Up @@ -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

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/app/app.module.ts
Expand Up @@ -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: [
Expand All @@ -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 },
],
},
Expand All @@ -32,6 +34,7 @@ import { CacheModule } from './cache';
// ChatModule,
ExternalModule,
NotificationsModule,
ProjectModule,
],
controllers: [AppController],
})
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/auth/auth.service.ts
Expand Up @@ -14,15 +14,15 @@ export class AuthService extends CrudService<User> {
async getLoggedUserOrCreate(token: JwtToken): Promise<User> {
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 {
const newUser = {
firstName: token.given_name,
lastName: token.family_name,
email: token.email,
userId: token.preferred_username,
username: token.preferred_username,
};
return super.create(newUser);
}
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/app/auth/dto/create-user.dto.ts
Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions apps/api/src/app/auth/guards/allow.guard.ts
Expand Up @@ -8,9 +8,9 @@ export class AllowGuard implements CanActivate {
constructor(private reflector: Reflector, private config: ConfigService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const endpointAllow = this.reflector.get<string[]>('allow', context.getHandler());
// const classEndpointAllow = this.reflector.get<string[]>('allow', context.getClass());
// const endpointAllows = [...endpointAllow, ...classEndpointAllow];
const endpointAllow =
this.reflector.get<string[]>('allow', context.getHandler()) ||
this.reflector.get<string[]>('allow', context.getClass());

if (endpointAllow) {
// skip for public
Expand Down
15 changes: 9 additions & 6 deletions apps/api/src/app/auth/guards/role.guard.ts
Expand Up @@ -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<boolean> {
const endpointRoles = this.reflector.get<string[]>('roles', context.getHandler());
const methodEndpointRoles = this.reflector.get<string[]>('roles', context.getHandler()) || [];
const classEndpointRoles = this.reflector.get<string[]>('roles', context.getClass()) || [];
const endpointRoles = [...methodEndpointRoles, ...classEndpointRoles];

if (!endpointRoles || endpointRoles.length === 0) {
return true;
}
Expand All @@ -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`);
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 5 additions & 5 deletions apps/api/src/app/auth/user.entity.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -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' })
Expand All @@ -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;
}
6 changes: 3 additions & 3 deletions apps/api/src/app/cache/cache-config.service.ts
Expand Up @@ -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') {
Expand All @@ -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
};
}
Expand Down
52 changes: 52 additions & 0 deletions 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<T> = (...args) => Observable<T>;

export function Cache<T>(options?: CacheManagerOptions) {
return (target: any, methodName: string, descriptor: TypedPropertyDescriptor<Cacheable<T>>) => {
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<T>(cacheKey)).pipe(
switchMap(res =>
res
? of(res)
: originalMethod
.apply(this, args)
.pipe(tap((methodResult: T) => cache.set<T>(cacheKey, methodResult, options))),
),
);
}
};

return descriptor;
};
}

export function CacheBuster<T>(cacheKey: string) {
return (target: any, methodName: string, descriptor: TypedPropertyDescriptor<Cacheable<T>>) => {
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
return originalMethod.apply(this, args).pipe(tap(this.cacheService.del(cacheKey)));
};
return descriptor;
};
}
14 changes: 13 additions & 1 deletion apps/api/src/app/cache/cache.service.spec.ts
Expand Up @@ -5,11 +5,15 @@ describe('CacheService', () => {

let store: any = {};

const Manager = jest.fn<ICacheManager>().mockImplementation(() => {
const Manager = jest.fn<ICacheManager, any[]>(() => {
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];
}),
};
});
Expand Down Expand Up @@ -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;

Expand Down
17 changes: 11 additions & 6 deletions 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<T>(key: string): Promise<T>;
set<T>(key: string, value: T, options?: CacheManagerOptions): Promise<T>;
del(key: string): Promise<void>;
}

@Injectable()
Expand All @@ -14,11 +15,15 @@ export class CacheService {
this.cache = cache;
}

public get(key: string): Promise<any> {
get<T>(key: string): Promise<T> {
return this.cache.get(key);
}

public set(key: string, value: any, options?: { ttl: number }): Promise<any> {
set<T>(key: string, value: T, options?: CacheManagerOptions): Promise<T> {
return this.cache.set(key, value, options);
}

del(key: string): Promise<void> {
return this.cache.del(key);
}
}
1 change: 1 addition & 0 deletions apps/api/src/app/cache/index.ts
@@ -1,2 +1,3 @@
export * from './cache.service';
export * from './cache.decorator';
export * from './cache.module';

0 comments on commit c7340b3

Please sign in to comment.