diff --git a/apps/api/src/app/commands/create-admin.ts b/apps/api/src/app/commands/create-admin.ts new file mode 100755 index 000000000..141a9957d --- /dev/null +++ b/apps/api/src/app/commands/create-admin.ts @@ -0,0 +1,39 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from '../app.module'; +import { cli } from 'cli-ux'; +import { UserService } from '../user'; + +/** + * Create admin account using command line interface + */ +const main = async () => { + const app = await NestFactory.createApplicationContext(AppModule, { + logger: false, + }); + + const firstName = await cli.prompt('First name'); + const lastName = await cli.prompt('Last name'); + const email = await cli.prompt('Email'); + const username = await cli.prompt('Username'); + const password = await cli.prompt('Password', { type: 'mask' }); + + try { + cli.action.start('creating an admin'); + + await app.get(UserService).create({ + firstName, + lastName, + email, + username, + }); + + cli.action.stop(); + } catch (error) { + cli.action.stop(); + console.error(error); + } finally { + await app.close(); + } +}; + +main(); diff --git a/apps/api/src/app/auth/passport/openid.strategy.ts b/apps/api/src/app/commands/migrations.ts similarity index 100% rename from apps/api/src/app/auth/passport/openid.strategy.ts rename to apps/api/src/app/commands/migrations.ts diff --git a/apps/api/src/app/notifications/notification/commands/handlers/index.ts b/apps/api/src/app/notifications/notification/commands/handlers/index.ts new file mode 100644 index 000000000..64a83d0d8 --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/handlers/index.ts @@ -0,0 +1,5 @@ +import { NotificationsHandler } from './notifications.handler'; +import { NotificationsDeleteHandler } from './notifications-delete.handler'; +import { NotificationsMarkAsReadHandler } from './notifications-make-as-read.handler'; + +export const CommandHandlers = [NotificationsHandler, NotificationsDeleteHandler, NotificationsMarkAsReadHandler]; diff --git a/apps/api/src/app/notifications/notification/commands/handlers/notifications-delete.handler.ts b/apps/api/src/app/notifications/notification/commands/handlers/notifications-delete.handler.ts new file mode 100644 index 000000000..85f75584e --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/handlers/notifications-delete.handler.ts @@ -0,0 +1,14 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { NotificationService } from '../../notification.service'; +import { NotificationsDeleteCommand } from '../notifications-delete.command'; + +@CommandHandler(NotificationsDeleteCommand) +export class NotificationsDeleteHandler implements ICommandHandler { + constructor(private readonly notificationService: NotificationService) {} + + public async execute(command: NotificationsDeleteCommand): Promise { + console.log('command:DeleteNotificationCommand', command); + await this.notificationService.onDeleteNotification(command); + } +} diff --git a/apps/api/src/app/notifications/notification/commands/handlers/notifications-make-as-read.handler.ts b/apps/api/src/app/notifications/notification/commands/handlers/notifications-make-as-read.handler.ts new file mode 100644 index 000000000..00d8e6ea3 --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/handlers/notifications-make-as-read.handler.ts @@ -0,0 +1,13 @@ +import { NotificationService } from '../../notification.service'; +import { NotificationsMarkAsReadCommand } from '../notifications-make-as-read.command'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +@CommandHandler(NotificationsMarkAsReadCommand) +export class NotificationsMarkAsReadHandler implements ICommandHandler { + constructor(private readonly notificationService: NotificationService) {} + + public async execute(command: NotificationsMarkAsReadCommand): Promise { + console.log('command:NotificationsMarkAsReadCommand', command); + await this.notificationService.onMarkAsRead(command); + } +} diff --git a/apps/api/src/app/notifications/notification/commands/handlers/notifications.handler.ts b/apps/api/src/app/notifications/notification/commands/handlers/notifications.handler.ts new file mode 100644 index 000000000..244ec41eb --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/handlers/notifications.handler.ts @@ -0,0 +1,28 @@ +import { NotificationService } from '../../notification.service'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { GenericCommand } from '../../../../shared'; +import { NotificationsDeleteCommand } from '../notifications-delete.command'; +import { NotificationsMarkAsReadCommand } from '../notifications-make-as-read.command'; +import { Logger } from '@nestjs/common'; + +@CommandHandler(GenericCommand) +export class NotificationsHandler implements ICommandHandler { + private readonly logger = new Logger(NotificationsHandler.name); + constructor(private readonly notificationService: NotificationService) {} + + public async execute(command: GenericCommand): Promise { + const { type, payload, user } = command; + switch (type) { + case NotificationsDeleteCommand.type: { + return await this.notificationService.onMarkAsRead(new NotificationsDeleteCommand(payload, user)); + } + case NotificationsMarkAsReadCommand.type: { + return await this.notificationService.onMarkAsRead(new NotificationsMarkAsReadCommand(payload, user)); + } + default: { + this.logger.error('received unknown command: ', command.type); + // return this.commandBus.execute(command); + } + } + } +} diff --git a/apps/api/src/app/notifications/notification/commands/index.ts b/apps/api/src/app/notifications/notification/commands/index.ts new file mode 100644 index 000000000..7efe4b253 --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/index.ts @@ -0,0 +1,2 @@ +export { NotificationsDeleteCommand } from './notifications-delete.command' +export { NotificationsMarkAsReadCommand } from './notifications-make-as-read.command' diff --git a/apps/api/src/app/notifications/notification/commands/notifications-delete.command.ts b/apps/api/src/app/notifications/notification/commands/notifications-delete.command.ts new file mode 100644 index 000000000..cb878350e --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/notifications-delete.command.ts @@ -0,0 +1,7 @@ +import { ICommand } from '@nestjs/cqrs'; +import { User } from '@ngx-starter-kit/models'; + +export class NotificationsDeleteCommand implements ICommand { + static readonly type = '[Notifications] Delete'; + constructor(public readonly payload: any, public readonly user: User) {} +} diff --git a/apps/api/src/app/notifications/notification/commands/notifications-make-as-read.command.ts b/apps/api/src/app/notifications/notification/commands/notifications-make-as-read.command.ts new file mode 100644 index 000000000..3e39a4d8a --- /dev/null +++ b/apps/api/src/app/notifications/notification/commands/notifications-make-as-read.command.ts @@ -0,0 +1,7 @@ +import { ICommand } from '@nestjs/cqrs'; +import { User } from '@ngx-starter-kit/models'; + +export class NotificationsMarkAsReadCommand implements ICommand { + static readonly type = '[Notifications] MarkAsRead'; + constructor(public readonly payload: any, public readonly user: User) {} +} diff --git a/apps/api/src/app/notifications/notification/dto/send-notification.dto.ts b/apps/api/src/app/notifications/notification/dto/send-notification.dto.ts index ee8fc657d..7c02d8489 100644 --- a/apps/api/src/app/notifications/notification/dto/send-notification.dto.ts +++ b/apps/api/src/app/notifications/notification/dto/send-notification.dto.ts @@ -2,9 +2,9 @@ import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class SendNotificationDto { - @ApiModelProperty({ type: Number }) + @ApiModelProperty({ type: String }) @IsNotEmpty() - id: number; + id: string; @ApiModelPropertyOptional({ type: String }) @IsOptional() diff --git a/apps/api/src/app/notifications/notification/notification.controller.ts b/apps/api/src/app/notifications/notification/notification.controller.ts index 5270311d1..c0c6e2f64 100644 --- a/apps/api/src/app/notifications/notification/notification.controller.ts +++ b/apps/api/src/app/notifications/notification/notification.controller.ts @@ -4,12 +4,14 @@ import { ApiExcludeEndpoint, ApiOAuth2Auth, ApiOperation, ApiResponse, ApiUseTag import { Notification } from './notification.entity'; import { CreateNotificationDto } from './dto/create-notification.dto'; import { NotificationService } from './notification.service'; -import { CurrentUser, Roles, RolesEnum, User } from '../../auth'; +import { CurrentUser, Roles, RolesEnum } from '../../auth'; import { SendNotificationDto } from './dto/send-notification.dto'; import { UpdateNotificationDto } from './dto/update-notification.dto'; import { NotificationList } from './dto/notification-list.model'; import { FindNotificationsDto } from './dto/find-notifications.dto'; import { FindOwnNotificationsDto } from './dto/find-own-notifications.dto'; +import { User } from '@ngx-starter-kit/models'; +import { UUIDValidationPipe } from '../../shared'; @ApiOAuth2Auth(['read']) @ApiUseTags('Notifications') @@ -32,7 +34,7 @@ export class NotificationController extends CrudController { return this.notificationService.findAll(filter); } - @ApiOperation({ title: 'find user\'s and global Notifications' }) + @ApiOperation({ title: "find user's and global Notifications" }) @ApiResponse({ status: HttpStatus.OK, description: 'Find matching Notifications', type: NotificationList }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'No matching records found' }) @Get('own') @@ -47,7 +49,7 @@ export class NotificationController extends CrudController { @ApiUseTags('Admin') @Roles(RolesEnum.ADMIN) @Get(':id') - async findById(@Param('id') id: string): Promise { + async findById(@Param('id', UUIDValidationPipe) id: string): Promise { return super.findById(id); } @@ -90,7 +92,7 @@ export class NotificationController extends CrudController { @Roles(RolesEnum.ADMIN) @Delete(':id') async deleteByAdmin(@Param('id') id: string): Promise { - return this.notificationService.update({ id: parseInt(id, 10) }, { isActive: false }); + return this.notificationService.update(id, { isActive: false }); } // @ApiOperation({ title: 'Delete record by user' }) diff --git a/apps/api/src/app/notifications/notification/notification.entity.ts b/apps/api/src/app/notifications/notification/notification.entity.ts index 08090058c..2fcf533a9 100644 --- a/apps/api/src/app/notifications/notification/notification.entity.ts +++ b/apps/api/src/app/notifications/notification/notification.entity.ts @@ -1,5 +1,5 @@ import { Column, CreateDateColumn, Entity, Index, UpdateDateColumn, VersionColumn } from 'typeorm'; -import { Base } from '../../core/entities/base.entity'; +import { Base } from '../../core/entities/base'; import { ApiModelProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; diff --git a/apps/api/src/app/notifications/notification/notification.service.ts b/apps/api/src/app/notifications/notification/notification.service.ts index 310470d38..7d98e553b 100644 --- a/apps/api/src/app/notifications/notification/notification.service.ts +++ b/apps/api/src/app/notifications/notification/notification.service.ts @@ -1,31 +1,23 @@ -import { - Injectable, - Logger, - OnApplicationBootstrap, - OnApplicationShutdown, - OnModuleDestroy, - OnModuleInit, -} from '@nestjs/common'; +import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Any, In, Repository } from 'typeorm'; import { CrudService, IPagination } from '../../core'; import { Notification, TargetType } from './notification.entity'; -import { EventBusGateway } from '../../shared'; -import { DeleteNotification, MarkAsRead } from '../index'; -import { User } from '../../auth'; +import { CQRSGateway } from '../../shared'; import { SubscriptionService } from '../subscription/subscription.service'; import { PushService } from './push.service'; import { Subscription } from '../subscription/subscription.entity'; import { FindOwnNotificationsDto } from './dto/find-own-notifications.dto'; import { FindNotificationsDto } from './dto/find-notifications.dto'; +import { User } from '@ngx-starter-kit/models'; +import { NotificationsDeleteCommand, NotificationsMarkAsReadCommand } from './commands'; @Injectable() -export class NotificationService extends CrudService - implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap, OnApplicationShutdown { +export class NotificationService extends CrudService implements OnModuleInit, OnModuleDestroy { readonly logger = new Logger(NotificationService.name); constructor( private readonly pushService: PushService, - private readonly eventBus: EventBusGateway, + private readonly cqrsGateway: CQRSGateway, private readonly subscriptionService: SubscriptionService, @InjectRepository(Notification) private readonly notificationsRepository: Repository, @InjectRepository(Subscription) private readonly subscriptionRepository: Repository, @@ -33,42 +25,20 @@ export class NotificationService extends CrudService super(notificationsRepository); } - async onApplicationBootstrap(): Promise { - // DO some async task - this.logger.log('in ApplicationBootstrap, done'); - } + onModuleInit() {} - onApplicationShutdown(signal: string) { - console.log('in onApplicationShutdown, signal: ', signal); // e.g. "SIGINT" - // process.kill(process.pid, 'SIGINT'); - } - - onModuleInit() { - this.eventBus.on(MarkAsRead.type, this.onMarkAsRead.bind(this)); - this.eventBus.on(DeleteNotification.type, this.onDeleteNotification.bind(this)); - } - - onModuleDestroy() { - this.eventBus.off(MarkAsRead.type, this.onMarkAsRead.bind(this)); - this.eventBus.off(DeleteNotification.type, this.onDeleteNotification.bind(this)); - } + onModuleDestroy() {} async findAll({ take, skip, order, ...where }: FindNotificationsDto): Promise> { return super.findAll({ where, take, skip, order }); } async findOwn({ take, skip, order }: FindOwnNotificationsDto, actor: User): Promise> { - const criteria = new FindNotificationsDto({ - target: In(['all', actor.username]), - isActive: true, - take, - skip, - order, - }); - return super.findAll(criteria); + const where = { isActive: true, target: In(['all', actor.username]) }; + return super.findAll({ where, take, skip, order }); } - async send(id: string | number) { + async send(id: string) { const notification = await this.findOne(id); const pushNotification = { @@ -100,15 +70,16 @@ export class NotificationService extends CrudService }); } - async onMarkAsRead(action: MarkAsRead, user: User) { + async onMarkAsRead(command: NotificationsMarkAsReadCommand) { await this.update( - { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.username }, + { id: command.payload.id, targetType: TargetType.USER, target: command.user.username }, { read: true }, ); } - async onDeleteNotification(action: DeleteNotification, user: User) { + + async onDeleteNotification(command: NotificationsDeleteCommand) { await this.update( - { id: parseInt(action.payload.id, 10), targetType: TargetType.USER, target: user.username }, + { id: command.payload.id, targetType: TargetType.USER, target: command.user.username }, { isActive: false }, ); } diff --git a/apps/api/src/app/notifications/notifications.module.ts b/apps/api/src/app/notifications/notifications.module.ts index 28f390c7f..c71cf5e07 100644 --- a/apps/api/src/app/notifications/notifications.module.ts +++ b/apps/api/src/app/notifications/notifications.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { CqrsModule } from '@nestjs/cqrs'; import { SharedModule } from '../shared'; import { NotificationController } from './notification/notification.controller'; import { NotificationService } from './notification/notification.service'; +import { CommandHandlers } from './notification/commands/handlers'; import { SubscriptionController } from './subscription/subscription.controller'; import { SubscriptionService } from './subscription/subscription.service'; import { PushService } from './notification/push.service'; @@ -10,9 +12,8 @@ import { Notification } from './notification/notification.entity'; import { Subscription } from './subscription/subscription.entity'; @Module({ - // imports: [SharedModule, TypeOrmModule.forFeature([NotificationsRepository])], - imports: [SharedModule, TypeOrmModule.forFeature([Notification, Subscription])], - providers: [PushService, SubscriptionService, NotificationService], + imports: [SharedModule, CqrsModule, TypeOrmModule.forFeature([Notification, Subscription])], + providers: [PushService, SubscriptionService, NotificationService, ...CommandHandlers], controllers: [NotificationController, SubscriptionController], }) export class NotificationsModule {} diff --git a/apps/api/src/app/notifications/subscription/subscription.controller.ts b/apps/api/src/app/notifications/subscription/subscription.controller.ts index ca7a70598..5eb8ac9b8 100644 --- a/apps/api/src/app/notifications/subscription/subscription.controller.ts +++ b/apps/api/src/app/notifications/subscription/subscription.controller.ts @@ -4,12 +4,12 @@ import { CrudController } from '../../core'; import { Subscription } from './subscription.entity'; import { SubscriptionService } from './subscription.service'; import { CurrentUser, Roles, RolesEnum } from '../../auth/decorators'; -import { User } from '../../auth'; import { CreateSubscriptionDto } from './dto/create-subscription.dto'; import { UpdateSubscriptionDto } from './dto/update-subscription.dto'; import { SubscriptionList } from './dto/subscription-list.model'; import { FindSubscriptionsDto } from './dto/find-subscriptions.dto'; import { FindOwnSubscriptionsDto } from './dto/find-own-subscriptions.dto'; +import { User } from '@ngx-starter-kit/models'; @ApiOAuth2Auth(['read']) @ApiUseTags('Subscription') @@ -88,7 +88,7 @@ export class SubscriptionController extends CrudController { if (id.startsWith('http')) { return this.subscriptionService.update({ endpoint: id, username: user.username }, entity); } else { - return this.subscriptionService.update({ id: parseInt(id, 10), username: user.username }, entity); + return this.subscriptionService.update({ id, username: user.username }, entity); } } @@ -100,7 +100,7 @@ export class SubscriptionController extends CrudController { if (id.startsWith('http')) { return this.subscriptionService.delete({ endpoint: id, username: user.username }); } else { - return this.subscriptionService.delete({ id: parseInt(id, 10), username: user.username }); + return this.subscriptionService.delete({ id, 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 d3c38b818..c05139041 100644 --- a/apps/api/src/app/notifications/subscription/subscription.entity.ts +++ b/apps/api/src/app/notifications/subscription/subscription.entity.ts @@ -1,7 +1,7 @@ import { Column, CreateDateColumn, Entity, Index, UpdateDateColumn, VersionColumn } from 'typeorm'; import { ApiModelProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; -import { Base } from '../../core/entities/base.entity'; +import { Base } from '../../core/entities/base'; @Entity('subscription') export class Subscription extends Base { diff --git a/apps/api/src/app/notifications/subscription/subscription.service.ts b/apps/api/src/app/notifications/subscription/subscription.service.ts index ddf7c5984..75fb0534a 100644 --- a/apps/api/src/app/notifications/subscription/subscription.service.ts +++ b/apps/api/src/app/notifications/subscription/subscription.service.ts @@ -5,9 +5,9 @@ import { Subscription } from './subscription.entity'; import { FindConditions, Repository } from 'typeorm'; import { setVapidDetails } from 'web-push'; import { environment as env } from '@env-api/environment'; -import { User } from '../../auth'; import { FindOwnSubscriptionsDto } from './dto/find-own-subscriptions.dto'; import { FindSubscriptionsDto } from './dto/find-subscriptions.dto'; +import { User } from '@ngx-starter-kit/models'; @Injectable() export class SubscriptionService extends CrudService { diff --git a/apps/api/src/app/shared/commands/generic.command.ts b/apps/api/src/app/shared/commands/generic.command.ts new file mode 100644 index 000000000..4a62c3557 --- /dev/null +++ b/apps/api/src/app/shared/commands/generic.command.ts @@ -0,0 +1,6 @@ +import { ICommand } from '@nestjs/cqrs'; +import { User } from '@ngx-starter-kit/models'; + +export class GenericCommand implements ICommand { + constructor(public readonly type: string, public readonly payload: any, public readonly user: User) {} +} diff --git a/apps/api/src/app/shared/eventbus.gateway.ts b/apps/api/src/app/shared/cqrs.gateway.ts similarity index 51% rename from apps/api/src/app/shared/eventbus.gateway.ts rename to apps/api/src/app/shared/cqrs.gateway.ts index 85345b45a..564ecf4db 100644 --- a/apps/api/src/app/shared/eventbus.gateway.ts +++ b/apps/api/src/app/shared/cqrs.gateway.ts @@ -7,27 +7,33 @@ import { WebSocketServer, WsResponse, } from '@nestjs/websockets'; +import { CommandBus, EventBus } from '@nestjs/cqrs'; import { Observable, of } from 'rxjs'; -import { EventEmitter } from 'events'; import { Logger, UseGuards } from '@nestjs/common'; import { delay } from 'rxjs/operators'; import { ISocket } from './interfaces/socket.interface'; import { Server } from 'socket.io'; -import { AuthService, User, WsAuthGuard } from '../auth'; +import { WsAuthGuard } from '../auth'; +import { User } from '@ngx-starter-kit/models'; +import { GenericCommand } from './commands/generic.command'; +import { GenericEvent } from './events/generic.event'; + +// import { UserService } from '../user'; @WebSocketGateway({ namespace: 'eventbus' }) -export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { +export class CQRSGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { static EVENTS = 'events'; - static ACTIONS = 'actions'; - private readonly logger = new Logger(EventBusGateway.name); + static COMMANDS = 'actions'; + private readonly logger = new Logger(CQRSGateway.name); @WebSocketServer() server: Server; clients: ISocket[] = []; - constructor(/*private authService: AuthService*/) { - super(); - } + constructor( + private readonly eventBus: EventBus, + private readonly commandBus: CommandBus /*private userService: UserService*/, + ) {} afterInit(server) {} @@ -59,25 +65,46 @@ export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGa return of({ event, data }).pipe(delay(1000)); } - @SubscribeMessage('actions') - onActions(client: ISocket, action: any) { - // this.logger.log(`actions => ${client.id} ${client.user.username} ${action.type} ${action.payload}`); - this.emit(action.type, action, client.user); + @SubscribeMessage(CQRSGateway.EVENTS) + onEvent(client: ISocket, event: any) { + // this.logger.log(`event => ${client.id} ${client.user.username} ${event.type} ${event.payload}`); + this.eventBus.publish(new GenericEvent(event.type, event.payload , client.user)); + } - sendActionToUser(user: User, action: any): void { + @SubscribeMessage(CQRSGateway.COMMANDS) + onCommand(client: ISocket, command: any) { + // this.logger.log(`command => ${client.id} ${client.user.username} ${command.type} ${command.payload}`); + this.commandBus.execute(new GenericCommand(command.type, command.payload, client.user )); + } + + sendCommandToUser(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 })); + clients.forEach(socket => (socket as any).emit(CQRSGateway.COMMANDS, { ...action, type })); } - sendActionToAll(action: any): void { + sendCommandToAll(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 })); + this.clients.forEach(socket => (socket as any).emit(CQRSGateway.COMMANDS, { ...action, type })); + } + + sendEventToUser(user: User, event: any): void { + const clients = this.getSocketsForUser(user); + const type = this.getActionTypeFromInstance(event); + // FIXME: remove any. only needed for docker build + clients.forEach(socket => (socket as any).emit(CQRSGateway.EVENTS, { ...event, type })); } + sendEventToAll(event: any): void { + const type = this.getActionTypeFromInstance(event); + // FIXME: remove any. only needed for docker build + this.clients.forEach(socket => (socket as any).emit(CQRSGateway.EVENTS, { ...event, type })); + } + + private getSocketsForUser(user: User): ISocket[] { return this.clients.filter(c => c.user && c.user.username === user.username); } @@ -89,5 +116,3 @@ export class EventBusGateway extends EventEmitter implements OnGatewayInit, OnGa return action.type; } } - -// https://github.com/evebook/api/blob/master/src/modules/websocket/websocket.gateway.ts diff --git a/apps/api/src/app/shared/decorators/orm-query.ts b/apps/api/src/app/shared/decorators/orm-query.ts new file mode 100644 index 000000000..a995ba412 --- /dev/null +++ b/apps/api/src/app/shared/decorators/orm-query.ts @@ -0,0 +1,6 @@ +import { createParamDecorator } from '@nestjs/common'; +import { QueryBuilder } from 'typeorm-express-query-builder'; + +export const ORMQuery = createParamDecorator((data, req) => { + return new QueryBuilder(req.query).build(); +}); diff --git a/apps/api/src/app/shared/events/generic.event.ts b/apps/api/src/app/shared/events/generic.event.ts new file mode 100644 index 000000000..16fd24265 --- /dev/null +++ b/apps/api/src/app/shared/events/generic.event.ts @@ -0,0 +1,6 @@ +import { IEvent } from '@nestjs/cqrs'; +import { User } from '@ngx-starter-kit/models'; + +export class GenericEvent implements IEvent { + constructor(public readonly type: string, public readonly payload: any, public readonly user: User) {} +} diff --git a/apps/api/src/app/shared/index.ts b/apps/api/src/app/shared/index.ts index 1a366bc0f..f9c29cef1 100644 --- a/apps/api/src/app/shared/index.ts +++ b/apps/api/src/app/shared/index.ts @@ -1,2 +1,6 @@ export * from './shared.module'; -export { EventBusGateway } from './eventbus.gateway'; +export { CQRSGateway } from './cqrs.gateway'; +export { UUIDValidationPipe } from './pipes/uuid-validation.pipe'; +export { ORMQuery } from './decorators/orm-query'; +export { GenericCommand } from './commands/generic.command'; +export { GenericEvent } from './events/generic.event'; diff --git a/apps/api/src/app/shared/interfaces/socket.interface.ts b/apps/api/src/app/shared/interfaces/socket.interface.ts index 81895c28f..3333b3f95 100644 --- a/apps/api/src/app/shared/interfaces/socket.interface.ts +++ b/apps/api/src/app/shared/interfaces/socket.interface.ts @@ -1,7 +1,7 @@ -import { User } from '../../auth'; import { Socket } from 'socket.io'; +import { User, JwtToken } from '@ngx-starter-kit/models'; export interface ISocket extends Socket { user: User; - authInfo: any; + authInfo: JwtToken; } diff --git a/apps/api/src/app/core/pipes/uuid-validation.pipe.ts b/apps/api/src/app/shared/pipes/uuid-validation.pipe.ts similarity index 100% rename from apps/api/src/app/core/pipes/uuid-validation.pipe.ts rename to apps/api/src/app/shared/pipes/uuid-validation.pipe.ts diff --git a/apps/api/src/app/shared/shared.module.ts b/apps/api/src/app/shared/shared.module.ts index 49ea06145..cfa2235d7 100644 --- a/apps/api/src/app/shared/shared.module.ts +++ b/apps/api/src/app/shared/shared.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; -import { EventBusGateway } from './eventbus.gateway'; +import { CqrsModule } from '@nestjs/cqrs'; +import { CQRSGateway } from './cqrs.gateway'; +import { UUIDValidationPipe } from './pipes/uuid-validation.pipe'; import { AuthModule } from '../auth'; @Module({ - imports: [AuthModule], - providers: [EventBusGateway], - exports: [EventBusGateway], + imports: [CqrsModule, AuthModule], + providers: [CQRSGateway, UUIDValidationPipe], + exports: [CQRSGateway, UUIDValidationPipe], }) export class SharedModule {} diff --git a/apps/api/src/environments/environment.prod.ts b/apps/api/src/environments/environment.prod.ts index 48fcd679f..41cf9cc3f 100644 --- a/apps/api/src/environments/environment.prod.ts +++ b/apps/api/src/environments/environment.prod.ts @@ -5,7 +5,7 @@ export const environment: IEnvironment = { envName: 'prod', env: { - NODE_TLS_REJECT_UNAUTHORIZED: '0' + NODE_TLS_REJECT_UNAUTHORIZED: '0', }, server: { @@ -25,12 +25,13 @@ export const environment: IEnvironment = { keepConnectionAlive: true, logging: process.env.TYPEORM_LOGGING ? JSON.parse(process.env.TYPEORM_LOGGING) : false, synchronize: false, + uuidExtension: 'pgcrypto', }, auth: { + clientId: process.env.OIDC_CLIENT_ID || 'ngxapi', issuer: process.env.OIDC_ISSUER_URL || 'https://keycloak-ngx1.1d35.starter-us-east-1.openshiftapps.com/auth/realms/ngx', - clientId: process.env.OIDC_CLIENT_ID || 'ngxapi', }, email: { diff --git a/apps/api/src/environments/environment.ts b/apps/api/src/environments/environment.ts index 4fb57f12f..e565edd86 100644 --- a/apps/api/src/environments/environment.ts +++ b/apps/api/src/environments/environment.ts @@ -9,7 +9,7 @@ export const environment: IEnvironment = { env: { LOG_LEVEL: 'debug', - NODE_TLS_REJECT_UNAUTHORIZED: '0' + NODE_TLS_REJECT_UNAUTHORIZED: '0', }, ALLOW_WHITE_LIST: ['::ffff:127.0.0.1', '::1'], @@ -31,6 +31,7 @@ export const environment: IEnvironment = { keepConnectionAlive: true, logging: true, synchronize: true, + uuidExtension: 'pgcrypto', }, auth: {