From 04c51839e49c43e49d1f45cf6b9491b119873c04 Mon Sep 17 00:00:00 2001 From: xmlking Date: Mon, 18 Feb 2019 00:55:12 -0800 Subject: [PATCH] feat(api): adding CacheModule --- apps/api/API-TESTING.md | 4 +- apps/api/src/app/auth/auth.controller.ts | 2 +- apps/api/src/app/auth/user.entity.ts | 40 ++++++++++--- .../app/cache/cache-config.service.spec.ts | 15 +++++ .../api/src/app/cache/cache-config.service.ts | 34 +++++++++++ apps/api/src/app/cache/cache.module.ts | 16 ++++++ apps/api/src/app/cache/cache.service.spec.ts | 57 +++++++++++++++++++ apps/api/src/app/cache/cache.service.ts | 24 ++++++++ apps/api/src/app/cache/index.ts | 2 + apps/api/src/app/core/crud/crud.controller.ts | 9 +-- apps/api/src/app/core/crud/crud.service.ts | 32 +++++++++-- apps/api/src/app/core/crud/icrud.service.ts | 10 ++++ apps/api/src/app/core/crud/icube.service.ts | 9 --- .../app/core/entities/audit-base.entity.ts | 40 ++++++++++--- apps/api/src/app/core/entities/base.entity.ts | 2 +- .../external/kubernetes/kubernetes.service.ts | 2 +- .../notification/notification.controller.ts | 2 +- .../subscription/subscription.controller.ts | 8 ++- apps/api/src/app/user/README.md | 42 ++++++++++++++ .../src/app/user/email/email.controller.ts | 2 +- apps/api/src/app/user/user.module.ts | 7 +-- 21 files changed, 312 insertions(+), 47 deletions(-) create mode 100755 apps/api/src/app/cache/cache-config.service.spec.ts create mode 100755 apps/api/src/app/cache/cache-config.service.ts create mode 100755 apps/api/src/app/cache/cache.module.ts create mode 100755 apps/api/src/app/cache/cache.service.spec.ts create mode 100755 apps/api/src/app/cache/cache.service.ts create mode 100644 apps/api/src/app/cache/index.ts create mode 100644 apps/api/src/app/core/crud/icrud.service.ts delete mode 100644 apps/api/src/app/core/crud/icube.service.ts create mode 100644 apps/api/src/app/user/README.md diff --git a/apps/api/API-TESTING.md b/apps/api/API-TESTING.md index 01753ca41..40d80a8ce 100644 --- a/apps/api/API-TESTING.md +++ b/apps/api/API-TESTING.md @@ -90,7 +90,7 @@ curl -v -X POST \ ```bash OIDC_ISSUER_URL=https://keycloak-ngx.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx -OIDC_CLIENT_ID=ngxapp +OIDC_CLIENT_ID=ngxapi USERNAME=sumo3 PASSWORD=demo @@ -105,7 +105,7 @@ response=$(curl -X POST $OIDC_ISSUER_URL/protocol/openid-connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d username=$USERNAME \ -d password=$PASSWORD \ - -d client_id=OIDC_CLIENT_ID \ + -d client_id=$OIDC_CLIENT_ID \ -d 'grant_type=password' \ -d 'scope=openid') diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index d2a85537e..94a2d6178 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -7,7 +7,7 @@ import { UpdateUserDto } from './dto/update-user.dto'; import { CreateUserDto } from './dto/create-user.dto'; @ApiOAuth2Auth(['read']) -@ApiUseTags('Sumo', 'Auth') +@ApiUseTags('Auth') @Controller() export class AuthController extends CrudController { constructor(private readonly authService: AuthService) { diff --git a/apps/api/src/app/auth/user.entity.ts b/apps/api/src/app/auth/user.entity.ts index 0b5038e78..76ae10293 100644 --- a/apps/api/src/app/auth/user.entity.ts +++ b/apps/api/src/app/auth/user.entity.ts @@ -1,16 +1,24 @@ -import { Column, CreateDateColumn, Entity, Index, OneToMany, UpdateDateColumn, VersionColumn } from 'typeorm'; +import { + Column, + CreateDateColumn, + Entity, + Index, + OneToMany, + RelationId, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; import { ApiModelProperty } from '@nestjs/swagger'; import { IsAscii, IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; import { Base } from '../core/entities/base.entity'; - -export enum AccountSourceType { - msId, - hsId, - gitHub, -} +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'; @Entity('user') -export class User extends Base { +export class User extends Base implements IUser { @ApiModelProperty({ type: String }) @IsString() @IsNotEmpty() @@ -41,12 +49,28 @@ export class User extends Base { @Column() userId: string; + @ApiModelProperty({ type: 'string', format: 'date-time', example: '2018-11-21T06:20:32.232Z' }) @CreateDateColumn({ type: 'timestamptz' }) createdAt?: Date; + @ApiModelProperty({ type: 'string', format: 'date-time', example: '2018-11-21T06:20:32.232Z' }) @UpdateDateColumn({ type: 'timestamptz' }) updatedAt?: Date; @VersionColumn() version?: number; + + @ApiModelProperty({ type: Image, isArray: true }) + @OneToMany(_ => Image, image => image.user) + images?: Image[]; + + @ApiModelProperty({ type: Profile }) + // FIXME: OneToOne downward cascade delete not implemented + @OneToOne(type => Profile, { cascade: ['insert', 'remove'], nullable: true, onDelete: 'SET NULL' }) + @JoinColumn() + profile?: Profile; + + @ApiModelProperty({ type: Number }) + @RelationId((user: User) => user.profile) + profileId?: number; } diff --git a/apps/api/src/app/cache/cache-config.service.spec.ts b/apps/api/src/app/cache/cache-config.service.spec.ts new file mode 100755 index 000000000..183112a96 --- /dev/null +++ b/apps/api/src/app/cache/cache-config.service.spec.ts @@ -0,0 +1,15 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CacheConfigService } from './cache-config.service'; + +describe('CacheConfigService', () => { + let service: CacheConfigService; + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CacheConfigService], + }).compile(); + service = module.get(CacheConfigService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/app/cache/cache-config.service.ts b/apps/api/src/app/cache/cache-config.service.ts new file mode 100755 index 000000000..f4e522087 --- /dev/null +++ b/apps/api/src/app/cache/cache-config.service.ts @@ -0,0 +1,34 @@ +import { CacheModuleOptions, CacheOptionsFactory, Injectable } from '@nestjs/common'; +// REf: https://github.com/kyle-mccarthy/nest-next-starter/tree/master/src/cache +@Injectable() +export class CacheConfigService implements CacheOptionsFactory { + constructor() {} + + /** + * 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() { + return { + retry_strategy: (options: any) => { + if (options.error && options.error.code === 'ECONNREFUSED') { + return new Error('The server refused the connection'); + } + if (options.total_retry_time > 1000 * 60) { + return new Error('Retry time exhausted'); + } + if (options.attempt > 2) { + return new Error('Max attempts exhausted'); + } + return Math.min(options.attempt * 100, 3000); + }, + }; + } + + public createCacheOptions(): CacheModuleOptions { + return { + ttl: 5, // seconds + max: 10, // maximum number of items in cache + }; + } +} diff --git a/apps/api/src/app/cache/cache.module.ts b/apps/api/src/app/cache/cache.module.ts new file mode 100755 index 000000000..af3f3972f --- /dev/null +++ b/apps/api/src/app/cache/cache.module.ts @@ -0,0 +1,16 @@ +import { CacheModule as NestCacheModule, Global, Module } from '@nestjs/common'; +import { CacheConfigService } from './cache-config.service'; +import { CacheService } from './cache.service'; + +@Global() +@Module({ + imports: [ + NestCacheModule.registerAsync({ + useClass: CacheConfigService, + inject: [CacheConfigService], + }), + ], + providers: [CacheConfigService, CacheService], + exports: [CacheService], +}) +export class CacheModule {} diff --git a/apps/api/src/app/cache/cache.service.spec.ts b/apps/api/src/app/cache/cache.service.spec.ts new file mode 100755 index 000000000..02f8f58d7 --- /dev/null +++ b/apps/api/src/app/cache/cache.service.spec.ts @@ -0,0 +1,57 @@ +import { CacheService, ICacheManager } from './cache.service'; + +describe('CacheService', () => { + let service: CacheService; + + let store: any = {}; + + const Manager = jest.fn().mockImplementation(() => { + return { + get: jest.fn((key: string) => store[key]), + set: jest.fn((key: string, value: any, options?: { ttl: number }) => { + store[key] = value; + }), + }; + }); + + const manager = new Manager(); + + beforeAll(async () => { + service = new CacheService(manager); + }); + + beforeEach(async () => { + store = {}; + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should call set', () => { + service.set('test', 'test'); + + expect(manager.set).toHaveBeenCalledTimes(1); + }); + + it('should call get', () => { + const res = service.get('test'); + + expect(res).toBeUndefined(); + expect(manager.get).toHaveBeenCalledTimes(1); + }); + + it('should get set value', () => { + store.testKey = 'test value'; + + expect(service.get('testKey')).toEqual('test value'); + }); + + it('set should overwrite existing value', () => { + store.current = 0; + + expect(store.current).toEqual(0); + service.set('current', 1); + expect(store.current).toEqual(1); + }); +}); diff --git a/apps/api/src/app/cache/cache.service.ts b/apps/api/src/app/cache/cache.service.ts new file mode 100755 index 000000000..6c2021a5a --- /dev/null +++ b/apps/api/src/app/cache/cache.service.ts @@ -0,0 +1,24 @@ +import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'; + +export interface ICacheManager { + store: any; + get(key: string): any; + set(key: string, value: string, options?: { ttl: number }): any; +} + +@Injectable() +export class CacheService { + private cache!: ICacheManager; + + constructor(@Inject(CACHE_MANAGER) cache: ICacheManager) { + this.cache = cache; + } + + public get(key: string): Promise { + return this.cache.get(key); + } + + public set(key: string, value: any, options?: { ttl: number }): Promise { + return this.cache.set(key, value, options); + } +} diff --git a/apps/api/src/app/cache/index.ts b/apps/api/src/app/cache/index.ts new file mode 100644 index 000000000..f469b7329 --- /dev/null +++ b/apps/api/src/app/cache/index.ts @@ -0,0 +1,2 @@ +export * from './cache.service'; +export * from './cache.module'; diff --git a/apps/api/src/app/core/crud/crud.controller.ts b/apps/api/src/app/core/crud/crud.controller.ts index a3c1dc623..8b835ed9f 100644 --- a/apps/api/src/app/core/crud/crud.controller.ts +++ b/apps/api/src/app/core/crud/crud.controller.ts @@ -2,7 +2,8 @@ import { Get, Post, Put, Delete, Body, Param, HttpStatus } from '@nestjs/common' import { ApiOperation, ApiResponse } from '@nestjs/swagger'; import { Base } from '../entities/base.entity'; import { DeepPartial } from 'typeorm'; -import { ICrudService } from './icube.service'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { ICrudService } from './icrud.service'; @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden' }) @@ -31,7 +32,7 @@ export abstract class CrudController { description: 'Invalid input, The response body may contain clues as to what went wrong', }) @Post() - async create(@Body() entity: DeepPartial, options?: any): Promise { + async create(@Body() entity: DeepPartial, ...options: any[]): Promise { return this.crudService.create(entity); } @@ -43,7 +44,7 @@ export abstract class CrudController { description: 'Invalid input, The response body may contain clues as to what went wrong', }) @Put(':id') - async update(@Param('id') id: string, @Body() entity: DeepPartial, options?: any): Promise { + async update(@Param('id') id: string, @Body() entity: QueryDeepPartialEntity, ...options: any[]): Promise { return this.crudService.update(id, entity); // FIXME: https://github.com/typeorm/typeorm/issues/1544 } @@ -51,7 +52,7 @@ export abstract class CrudController { @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 { + async delete(@Param('id') id: string, ...options: any[]): Promise { return this.crudService.delete(id); } } diff --git a/apps/api/src/app/core/crud/crud.service.ts b/apps/api/src/app/core/crud/crud.service.ts index 3e4b7adfd..98209a21e 100644 --- a/apps/api/src/app/core/crud/crud.service.ts +++ b/apps/api/src/app/core/crud/crud.service.ts @@ -8,8 +8,11 @@ import { Repository, UpdateResult, } from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { mergeMap } from 'rxjs/operators'; +import { of, throwError } from 'rxjs'; import { Base } from '../entities/base.entity'; -import { ICrudService } from './icube.service'; +import { ICrudService } from './icrud.service'; export abstract class CrudService implements ICrudService { protected constructor(protected readonly repository: Repository) {} @@ -33,7 +36,7 @@ export abstract class CrudService implements ICrudService { return record; } - public async create(entity: DeepPartial): Promise { + public async create(entity: DeepPartial, ...options: any[]): Promise { const obj = this.repository.create(entity); try { // https://github.com/Microsoft/TypeScript/issues/21592 @@ -43,19 +46,38 @@ export abstract class CrudService implements ICrudService { } } - public async update(id: string | number | FindConditions, entity: DeepPartial): Promise { + public async update( + id: string | number | FindConditions, + partialEntity: QueryDeepPartialEntity, + ...options: any[] + ): Promise { try { - return await this.repository.update(id, entity); + return await this.repository.update(id, partialEntity); } catch (err /*: WriteError*/) { throw new BadRequestException(err); } } - public async delete(criteria: string | number | FindConditions): Promise { + public async delete(criteria: string | number | FindConditions, ...options: any[]): Promise { try { return this.repository.delete(criteria); } catch (err) { throw new NotFoundException(`The record was not found`, err); } } + + /** + * e.g., findOneById(id).pipe(map(entity => entity.id), entityNotFound()) + */ + private entityNotFound() { + return stream$ => + stream$.pipe( + mergeMap(signal => { + if (!signal) { + return throwError(new NotFoundException(`The requested record was not found`)); + } + return of(signal); + }), + ); + } } diff --git a/apps/api/src/app/core/crud/icrud.service.ts b/apps/api/src/app/core/crud/icrud.service.ts new file mode 100644 index 000000000..a68452f60 --- /dev/null +++ b/apps/api/src/app/core/crud/icrud.service.ts @@ -0,0 +1,10 @@ +import { DeepPartial, DeleteResult, FindConditions, FindManyOptions, FindOneOptions, UpdateResult } from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +export interface ICrudService { + getAll(filter?: FindManyOptions): Promise<[T[], number]>; + getOne(id: string | number | FindOneOptions | FindConditions, options?: FindOneOptions): Promise; + create(entity: DeepPartial, ...options: any[]): Promise; + update(id: any, entity: QueryDeepPartialEntity, ...options: any[]): Promise; + delete(id: any, ...options: any[]): Promise; +} diff --git a/apps/api/src/app/core/crud/icube.service.ts b/apps/api/src/app/core/crud/icube.service.ts deleted file mode 100644 index 5f5b06121..000000000 --- a/apps/api/src/app/core/crud/icube.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DeepPartial, FindConditions, FindManyOptions, FindOneOptions } from 'typeorm'; - -export interface ICrudService { - getAll(filter?: FindManyOptions): Promise<[T[], number]>; - getOne(id: string | number | FindOneOptions | FindConditions, options?: FindOneOptions): Promise; - create(entity: DeepPartial): Promise; - update(id: any, entity: DeepPartial): Promise; - delete(id: any): Promise; -} 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 9f393db37..68d669489 100644 --- a/apps/api/src/app/core/entities/audit-base.entity.ts +++ b/apps/api/src/app/core/entities/audit-base.entity.ts @@ -1,8 +1,18 @@ -import { CreateDateColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from 'typeorm'; +import { + BeforeInsert, + BeforeUpdate, + Column, + CreateDateColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from 'typeorm'; import { Exclude } from 'class-transformer'; import { ApiModelProperty } from '@nestjs/swagger'; // FIXME: we need to import User like this to avoid Circular denpendence problem import { User } from '../../auth/user.entity'; +import { RequestContext } from '../context'; // TODO: Implement Soft Delete @@ -12,32 +22,46 @@ export abstract class AuditBase { id: number; @ApiModelProperty({ type: 'string', format: 'date-time', example: '2018-11-21T06:20:32.232Z' }) - // @Exclude() @CreateDateColumn({ type: 'timestamptz' }) createdAt?: Date; @ApiModelProperty({ type: 'string', format: 'date-time', example: '2018-11-21T06:20:32.232Z' }) - // @Exclude() @UpdateDateColumn({ type: 'timestamptz' }) updatedAt?: Date; - // @Exclude() + @ApiModelProperty({ type: User }) @ManyToOne(type => User, { onDelete: 'NO ACTION', onUpdate: 'CASCADE', nullable: false, }) - createdBy: User; + createdBy?: User; - // @Exclude() + @ApiModelProperty({ type: User }) @ManyToOne(type => User, { onDelete: 'NO ACTION', onUpdate: 'CASCADE', - nullable: false, + nullable: true, }) - updatedBy: User; + updatedBy?: User; @Exclude() @VersionColumn() version?: number; + + @BeforeInsert() + setCreatedByUser() { + const currentUser = RequestContext.currentUser(); + if (currentUser) { + this.createdBy = currentUser; + } + } + + @BeforeUpdate() + setUpdatedByUser() { + const currentUser = RequestContext.currentUser(); + if (currentUser) { + this.updatedBy = currentUser; + } + } } diff --git a/apps/api/src/app/core/entities/base.entity.ts b/apps/api/src/app/core/entities/base.entity.ts index b270bf5e5..56d865383 100644 --- a/apps/api/src/app/core/entities/base.entity.ts +++ b/apps/api/src/app/core/entities/base.entity.ts @@ -4,5 +4,5 @@ import { ApiModelPropertyOptional } from '@nestjs/swagger'; export abstract class Base { @ApiModelPropertyOptional() @PrimaryGeneratedColumn() - id: number; + id?: number; } diff --git a/apps/api/src/app/external/kubernetes/kubernetes.service.ts b/apps/api/src/app/external/kubernetes/kubernetes.service.ts index e96b3a414..a1337d20e 100644 --- a/apps/api/src/app/external/kubernetes/kubernetes.service.ts +++ b/apps/api/src/app/external/kubernetes/kubernetes.service.ts @@ -21,7 +21,7 @@ export class KubernetesService implements OnModuleInit { private readonly logger = new Logger(KubernetesService.name); private readonly clients = new Map( - Object.entries(env.kubernetes).map<[string, Api.Api]>(([key, value]) => [ + Object.entries(env.kubernetes).map<[string, Api.ApiRoot]>(([key, value]) => [ key, new Client({ config: { diff --git a/apps/api/src/app/notifications/notification/notification.controller.ts b/apps/api/src/app/notifications/notification/notification.controller.ts index ee745633f..65f90142e 100644 --- a/apps/api/src/app/notifications/notification/notification.controller.ts +++ b/apps/api/src/app/notifications/notification/notification.controller.ts @@ -9,7 +9,7 @@ import { SendNotificationDto } from './dto/send-notification.dto'; import { UpdateNotificationDto } from './dto/update-notification.dto'; @ApiOAuth2Auth(['read']) -@ApiUseTags('Sumo', 'Notifications') +@ApiUseTags('Notifications') @Controller('notifications') export class NotificationController extends CrudController { constructor(private readonly notificationService: NotificationService) { diff --git a/apps/api/src/app/notifications/subscription/subscription.controller.ts b/apps/api/src/app/notifications/subscription/subscription.controller.ts index 05ef585ee..8749601d9 100644 --- a/apps/api/src/app/notifications/subscription/subscription.controller.ts +++ b/apps/api/src/app/notifications/subscription/subscription.controller.ts @@ -9,7 +9,7 @@ import { CreateSubscriptionDto } from './dto/create-subscription.dto'; import { UpdateSubscriptionDto } from './dto/update-subscription.dto'; @ApiOAuth2Auth(['read']) -@ApiUseTags('Sumo', 'Subscription') +@ApiUseTags('Subscription') @Controller('subscription') export class SubscriptionController extends CrudController { constructor(private readonly subscriptionService: SubscriptionService) { @@ -26,7 +26,11 @@ export class SubscriptionController extends CrudController { } @ApiOperation({ title: 'find all user Subscriptions' }) - @ApiResponse({ status: HttpStatus.OK, description: 'All user Subscriptions', /* type: Subscription, */ isArray: true }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'All user Subscriptions', + /* type: Subscription, */ isArray: true, + }) @Get('user') async getUserSubscriptions(@CurrentUser() user): Promise<[Subscription[], number]> { return this.subscriptionService.getUserSubscriptions(user); diff --git a/apps/api/src/app/user/README.md b/apps/api/src/app/user/README.md new file mode 100644 index 000000000..c0ee9b61a --- /dev/null +++ b/apps/api/src/app/user/README.md @@ -0,0 +1,42 @@ +# User Module + + +### Profile + +```bash +# obtain and set token +access_token=11111 +``` + +> create profile. user with user e.g., `sumo3's` token +```bash +curl -X POST "http://localhost:3000/api/user/profile" \ +-H "accept: application/json" \ +-H "Content-Type: multipart/form-data" \ +-H "authorization: Bearer $access_token" \ +-F "file=@/Users/schinth/Documents/icons/amd_logo.png;type=image/png" \ +-F "gender=male" \ +-F "mobilePhone=1112223344" +``` + +> get profile. user with user e.g., `sumo3's` token +```bash +curl -X GET "http://localhost:3000/api/user/profile/1" \ +-H "accept: application/json" \ +-H "authorization: Bearer $access_token" +``` + +> list profiles. user with admin e.g., `ngxadmin's` token +```bash +curl -X GET "http://localhost:3000/api/user/profile" \ +-H "accept: application/json" \ +-H "authorization: Bearer $access_token" +``` + + +### Reference +* https://www.joshmorony.com/handling-multiple-file-uploads-with-nest-js/ +* https://www.joshmorony.com/displaying-upload-download-progress-in-an-ionic-application/ +* https://malcoded.com/posts/angular-file-upload-component-with-express +* https://github.com/LukasMarx/angular-file-upload +* https://github.com/apiel/wudo/blob/master/backend/src/resolver/auth.ts diff --git a/apps/api/src/app/user/email/email.controller.ts b/apps/api/src/app/user/email/email.controller.ts index 123c6630a..b7b3eb825 100644 --- a/apps/api/src/app/user/email/email.controller.ts +++ b/apps/api/src/app/user/email/email.controller.ts @@ -4,7 +4,7 @@ import { EmailDto } from './dto/email.dto'; import { ApiOAuth2Auth, ApiUseTags } from '@nestjs/swagger'; @ApiOAuth2Auth(['read']) -@ApiUseTags('Sumo', 'Email') +@ApiUseTags('Email') @Controller('email') export class EmailController { constructor(private readonly emailService: EmailService) {} diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 14ab82245..0e07d91bc 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -4,12 +4,11 @@ import { SharedModule } from '../shared'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ProfileController } from './profile/profile.controller'; import { ProfileService } from './profile/profile.service'; +import { Profile } from './profile/profile.entity'; +import { Image } from './profile/image.entity'; @Module({ - imports: [ - SharedModule, - // TypeOrmModule.forFeature([Profile, Image]), - ], + imports: [SharedModule, TypeOrmModule.forFeature([Profile, Image])], controllers: [EmailController, ProfileController], providers: [ProfileService], })