diff --git a/.env.example b/.env.example index d81f0fc..1d4d139 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,9 @@ DATABASE_PASSWORD=postgres WRITE_DATABASE_NAME=master-db READ_DATABASE_NAME=slave-db +WRITE_CONNECTION_NAME= +READ_CONNECTION_NAME= + TYPEORM_ENTITIES=/**/core/entities/**.entity{.ts,.js} TYPEORM_MIGRATIONS=/database/migrations/*{.ts,.js} TYPEORM_MIGRATIONS_DIR=src/database/migrations @@ -15,6 +18,8 @@ TYPEORM_LOGGING=true TYPEORM_SYNCHRONIZE=false TYPEORM_MIGRATION_RUN=false +JWT_SECRET_KEY=jwt-secret-key + DROPBOX_TOKEN= DROPBOX_KEY= DROPBOX_SECRET= diff --git a/.gitignore b/.gitignore index 6d3c07c..8d83aa9 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,6 @@ dist/* .env .env.development +.env.docker app.yaml \ No newline at end of file diff --git a/README.md b/README.md index 0a14344..e08a314 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ +# System Design + +## FUNCTION REQUIREMENTS: + + - user can login/logout + - user can post the article + - user can upload file + - user can searching + - user can like, comment, follow + +## NON FUNCTION REQUIREMENTS: + + - high availability, latency < 500ms + - total users: 100M + - daily active user: 1M + - 1 user upload 2 image per day + - image size < 5MB + --> Write 1 * 2 * 5MB * 1M = 10MB = 10TB + - QPS: Write: 1M * 2(image upload) / (24hours * 60 minutes * 60 seconds) == 25 QPS + - QPS: Read: 1M * 50 / (24hours * 60 minutes * 60 seconds) == 580 QPS + ==> READ HEAVY + - No lost for image + +## API Design + + Login: - request: { username, password} - response: { accessToken, refreshToken } + Logout - request: { token } - response: boolean + Post Article - request: { userId, title, description, content, hastag } - response: articleId + Upload File - request: { string base 64 } - response: file_url + Searching - request: { keyword } - response: [ article ] + Like - request: { userId, articleId } - response: boolean + Comment - request: { userId, articleId, content } - response: commentId | Comment + Follow - request: { currentUserId, toUserId } - response: boolean + +## DB Schema + +![alt text](./assets/db-schema.png) + +## Design system + +![alt text](./assets/architect.png) + # Getting started ## Installation @@ -25,6 +67,7 @@ Run migration --- ## Running on docker + Build images docker-compose build diff --git a/assets/architect.png b/assets/architect.png new file mode 100644 index 0000000..225d156 Binary files /dev/null and b/assets/architect.png differ diff --git a/assets/db-schema.png b/assets/db-schema.png new file mode 100644 index 0000000..3a0d1a2 Binary files /dev/null and b/assets/db-schema.png differ diff --git a/assets/func-non_func requirements.png b/assets/func-non_func requirements.png new file mode 100644 index 0000000..27f6c27 Binary files /dev/null and b/assets/func-non_func requirements.png differ diff --git a/docker-compose.yaml b/docker-compose.yaml index ed1bfa7..8942c85 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,10 +14,10 @@ services: - rabbitmq - redis env_file: - - .env - environment: - - NODE_ENV=staging - - DATABASE_HOST=host.docker.internal + - .env.docker + networks: + - social-network + social-api-2: build: context: . @@ -31,10 +31,9 @@ services: - rabbitmq - redis env_file: - - .env - environment: - - NODE_ENV=staging - - DATABASE_HOST=host.docker.internal + - .env.docker + networks: + - social-network db: container_name: social-postgres @@ -46,17 +45,26 @@ services: - /data/postgres/ ports: - 5432:5432 + networks: + - social-network + rabbitmq: container_name: social-rabbitmq image: rabbitmq:3-management ports: - "15672:15672" - "5672:5672" + networks: + - social-network + redis: container_name: social-redis image: "redis:alpine" ports: - "6379:6379" + networks: + - social-network + nginx: container_name: social-nginx build: ./nginx @@ -65,3 +73,9 @@ services: depends_on: - social-api-1 - social-api-2 + networks: + - social-network + +networks: + social-network: + driver: bridge diff --git a/package.json b/package.json index a754dbf..bcb3126 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,13 @@ "@nestjs/config": "^2.2.0", "@nestjs/core": "^7.0.5", "@nestjs/cqrs": "^9.0.1", + "@nestjs/microservices": "7.2", "@nestjs/platform-express": "^7.0.5", "@nestjs/swagger": "^4.4.0", "@nestjs/testing": "^7.0.5", "@nestjs/typeorm": "^7.0.0", "@nestjs/websockets": "^7.0.5", + "amqp-connection-manager": "^4.1.14", "amqplib": "^0.10.3", "argon2": "^0.26.2", "aws-sdk": "^2.1301.0", diff --git a/src/app.controller.ts b/src/app.controller.ts deleted file mode 100644 index 0cd25d1..0000000 --- a/src/app.controller.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Get, Controller } from '@nestjs/common'; - -@Controller() -export class AppController { - @Get() - root(): string { - return 'Hello World!'; - } -} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 379e85e..69236de 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,60 +1,24 @@ import { Module } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import * as dotenv from "dotenv"; -import { AppController } from "./app.controller"; + import { ArticleModule } from "./article/article.module"; -import { READ_CONNECTION, WRITE_CONNECTION } from "./config"; +import { DatabaseModule } from "./database/database.module"; import { MediaModule } from "./media/media.module"; import { ProfileModule } from "./profile/profile.module"; import { TagModule } from "./tag/tag.module"; import { UserModule } from "./user/user.module"; -dotenv.config(); - -const defaultOptions = { - type: process.env.DATABASE_ENGINE, - host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT), - username: process.env.DATABASE_USERNAME, - password: process.env.DATABASE_PASSWORD, - entities: [__dirname + process.env.TYPEORM_ENTITIES], - migrations: [__dirname + process.env.TYPEORM_MIGRATIONS], - logging: process.env.TYPEORM_LOGGING === "true", - synchronize: process.env.TYPEORM_SYNCHRONIZE === "true", - migrationsRun: process.env.TYPEORM_MIGRATION_RUN === "true", - migrationsTableName: "migrations", - cli: { - migrationsDir: process.env.TYPEORM_MIGRATIONS_DIR, - }, -}; - @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), - TypeOrmModule.forRootAsync({ - name: WRITE_CONNECTION, - useFactory: () => ({ - ...defaultOptions, - type: "postgres", - database: process.env.WRITE_DATABASE_NAME, - }), - }), - TypeOrmModule.forRootAsync({ - name: READ_CONNECTION, - useFactory: () => ({ - ...defaultOptions, - type: "postgres", - database: process.env.READ_DATABASE_NAME, - }), - }), + DatabaseModule, ArticleModule, UserModule, ProfileModule, TagModule, MediaModule, ], - controllers: [AppController], + controllers: [], providers: [], }) export class ApplicationModule {} diff --git a/src/article/commands/command.module.ts b/src/article/application/commands/command.module.ts similarity index 52% rename from src/article/commands/command.module.ts rename to src/article/application/commands/command.module.ts index 79d93d9..a3f93a4 100644 --- a/src/article/commands/command.module.ts +++ b/src/article/application/commands/command.module.ts @@ -1,14 +1,15 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { CommandHandlers } from "."; -import { WRITE_CONNECTION } from "../../config"; -import { FollowsEntity } from "../../profile/core/entities/follows.entity"; -import { RabbitMqModule } from "../../rabbitmq/rabbitmq.module"; -import { UserEntity } from "../../user/core"; -import { UserModule } from "../../user/user.module"; -import { ArticleEntity, BlockEntity } from "../core"; -import { CommentEntity } from "../core/entities/comment.entity"; + +import { CommandHandlers } from "./index"; +import { WRITE_CONNECTION } from "../../../configs"; +import { FollowsEntity } from "../../../profile/core/entities/follows.entity"; +import { RabbitMqModule } from "../../../rabbitmq/rabbitmq.module"; +import { UserEntity } from "../../../user/core"; +import { UserModule } from "../../../user/user.module"; +import { ArticleEntity, BlockEntity } from "../../core"; +import { CommentEntity } from "../../core/entities/comment.entity"; import { ArticleService } from "../services/article.service"; @Module({ diff --git a/src/article/commands/handlers/create-article.handler.ts b/src/article/application/commands/handlers/create-article.handler.ts similarity index 59% rename from src/article/commands/handlers/create-article.handler.ts rename to src/article/application/commands/handlers/create-article.handler.ts index f6c216c..8e3de35 100644 --- a/src/article/commands/handlers/create-article.handler.ts +++ b/src/article/application/commands/handlers/create-article.handler.ts @@ -1,13 +1,16 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { ArticleRO } from "../../core/interfaces/article.interface"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { ArticleEntity } from "../../../core/entities/article.entity"; +import { MessageCmd } from "../../../core/enums/article.enum"; +import { + ArticleRO, + IPayloadArticleCreated, +} from "../../../core/interfaces/article.interface"; import { ArticleService } from "../../services/article.service"; import { CreateArticleCommand } from "../impl"; @@ -18,10 +21,13 @@ export class CreateArticleCommandHandler constructor( @InjectRepository(ArticleEntity, WRITE_CONNECTION) private readonly articleRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ userId, @@ -39,10 +45,10 @@ export class CreateArticleCommandHandler ); if (article) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.ARTICLE_CREATED, - payload: { article }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.ARTICLE_CREATED }, + { article } + ); } return { diff --git a/src/article/commands/handlers/create-comment.handler.ts b/src/article/application/commands/handlers/create-comment.handler.ts similarity index 66% rename from src/article/commands/handlers/create-comment.handler.ts rename to src/article/application/commands/handlers/create-comment.handler.ts index 0044dcb..661667c 100644 --- a/src/article/commands/handlers/create-comment.handler.ts +++ b/src/article/application/commands/handlers/create-comment.handler.ts @@ -1,15 +1,14 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { CommentEntity } from "../../core/entities/comment.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { CommentRO } from "../../core/interfaces/article.interface"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities"; +import { ArticleEntity, CommentEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { CommentRO, IPayloadCommentCreated } from "../../../core/interfaces"; import { ArticleService } from "../../services/article.service"; import { CreateCommentCommand } from "../impl"; @@ -24,10 +23,13 @@ export class CreateCommentCommandHandler private readonly userRepository: Repository, @InjectRepository(CommentEntity, WRITE_CONNECTION) private readonly commentRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ userId, @@ -55,10 +57,10 @@ export class CreateCommentCommandHandler await this.commentRepository.save(comment); if (comment) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.COMMENT_CREATED, - payload: { comment }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.COMMENT_CREATED }, + { comment } + ); } const commentRO = this.articleService.buildCommentRO(comment); diff --git a/src/article/commands/handlers/delete-article.handler.ts b/src/article/application/commands/handlers/delete-article.handler.ts similarity index 75% rename from src/article/commands/handlers/delete-article.handler.ts rename to src/article/application/commands/handlers/delete-article.handler.ts index b8535f3..68d67e6 100644 --- a/src/article/commands/handlers/delete-article.handler.ts +++ b/src/article/application/commands/handlers/delete-article.handler.ts @@ -1,16 +1,17 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { DeleteResult, Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; import { - CommentEntity, ArticleEntity, BlockEntity, - MessageType, -} from "../../core"; + CommentEntity, + IPayloadArticleDeleted, + MessageCmd, +} from "../../../core"; import { DeleteArticleCommand } from "../impl"; @CommandHandler(DeleteArticleCommand) @@ -24,9 +25,11 @@ export class DeleteArticleCommandHandler private readonly blockRepository: Repository, @InjectRepository(CommentEntity, WRITE_CONNECTION) private readonly commentRepository: Repository, - - private readonly publisher: PublisherService - ) {} + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy + ) { + this.articleRmqClient.connect(); + } async execute({ userId, slug }: DeleteArticleCommand): Promise { try { @@ -58,10 +61,10 @@ export class DeleteArticleCommandHandler const _deleted = await this.articleRepository.delete({ slug: slug }); if (_deleted) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.ARTICLE_DELETED, - payload: { userId, slug }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.ARTICLE_DELETED }, + { userId, slug } + ); } return _deleted; diff --git a/src/article/commands/handlers/delete-comment.handler.ts b/src/article/application/commands/handlers/delete-comment.handler.ts similarity index 65% rename from src/article/commands/handlers/delete-comment.handler.ts rename to src/article/application/commands/handlers/delete-comment.handler.ts index 3547c90..e2b75a5 100644 --- a/src/article/commands/handlers/delete-comment.handler.ts +++ b/src/article/application/commands/handlers/delete-comment.handler.ts @@ -1,15 +1,13 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { CommentEntity } from "../../core/entities/comment.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { ArticleRO } from "../../core/interfaces/article.interface"; -import { ArticleService } from "../../services/article.service"; +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { ArticleEntity, CommentEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { ArticleRO, IPayloadCommentDeleted } from "../../../core/interfaces"; +import { ArticleService } from "../../services"; import { DeleteCommentCommand } from "../impl"; @CommandHandler(DeleteCommentCommand) @@ -21,10 +19,13 @@ export class DeleteCommentCommandHandler private readonly articleRepository: Repository, @InjectRepository(CommentEntity, WRITE_CONNECTION) private readonly commentRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ userId, @@ -56,12 +57,10 @@ export class DeleteCommentCommandHandler article = await this.articleRepository.save(article); if (_deleted && article) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.COMMENT_DELETED, - payload: { - comment: deleteComments[0], - }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.COMMENT_DELETED }, + { comment: deleteComments[0] } + ); } return { article: this.articleService.buildArticleRO(article) }; diff --git a/src/article/commands/handlers/favorite-article.handler.ts b/src/article/application/commands/handlers/favorite-article.handler.ts similarity index 64% rename from src/article/commands/handlers/favorite-article.handler.ts rename to src/article/application/commands/handlers/favorite-article.handler.ts index 2ff1214..ef279b8 100644 --- a/src/article/commands/handlers/favorite-article.handler.ts +++ b/src/article/application/commands/handlers/favorite-article.handler.ts @@ -1,15 +1,15 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { ArticleRO } from "../../core/interfaces/article.interface"; -import { ArticleService } from "../../services/article.service"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities"; +import { ArticleEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { ArticleRO, IPayloadArticleFavorited } from "../../../core/interfaces"; +import { ArticleService } from "../../services"; import { FavoriteArticleCommand } from "../impl"; @CommandHandler(FavoriteArticleCommand) @@ -21,10 +21,13 @@ export class FavoriteArticleCommandHandler private readonly articleRepository: Repository, @InjectRepository(UserEntity, WRITE_CONNECTION) private readonly userRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ userId, slug }: FavoriteArticleCommand): Promise { try { @@ -52,13 +55,10 @@ export class FavoriteArticleCommandHandler article = await this.articleRepository.save(article); if (_user && article) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.ARTICLE_FAVORITED, - payload: { - user, - article, - }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.ARTICLE_FAVORITED }, + { user, article } + ); } } diff --git a/src/article/commands/handlers/index.ts b/src/article/application/commands/handlers/index.ts similarity index 100% rename from src/article/commands/handlers/index.ts rename to src/article/application/commands/handlers/index.ts diff --git a/src/article/commands/handlers/unfavorite-article.handler.ts b/src/article/application/commands/handlers/unfavorite-article.handler.ts similarity index 62% rename from src/article/commands/handlers/unfavorite-article.handler.ts rename to src/article/application/commands/handlers/unfavorite-article.handler.ts index 73352ee..cefeb5a 100644 --- a/src/article/commands/handlers/unfavorite-article.handler.ts +++ b/src/article/application/commands/handlers/unfavorite-article.handler.ts @@ -1,14 +1,15 @@ +import { Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { ArticleRO } from "../../core/interfaces/article.interface"; -import { ArticleService } from "../../services/article.service"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities"; +import { ArticleEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { ArticleRO, IPayloadArticleFavorited } from "../../../core/interfaces"; +import { ArticleService } from "../../services"; import { UnFavoriteArticleCommand } from "../impl"; @CommandHandler(UnFavoriteArticleCommand) @@ -20,10 +21,13 @@ export class UnFavoriteArticleCommandHandler private readonly articleRepository: Repository, @InjectRepository(UserEntity, WRITE_CONNECTION) private readonly userRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ userId, @@ -51,13 +55,10 @@ export class UnFavoriteArticleCommandHandler article = await this.articleRepository.save(article); if (_user && article) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.ARTICLE_UNFAVORITED, - payload: { - user, - article, - }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.ARTICLE_UNFAVORITED }, + { user, article } + ); } } diff --git a/src/article/commands/handlers/update-article.handler.ts b/src/article/application/commands/handlers/update-article.handler.ts similarity index 56% rename from src/article/commands/handlers/update-article.handler.ts rename to src/article/application/commands/handlers/update-article.handler.ts index f40c735..48edf18 100644 --- a/src/article/commands/handlers/update-article.handler.ts +++ b/src/article/application/commands/handlers/update-article.handler.ts @@ -1,14 +1,14 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { MessageType } from "../../core/enums/article.enum"; -import { ArticleRO } from "../../core/interfaces/article.interface"; -import { ArticleService } from "../../services/article.service"; + +import { ARTICLE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { ArticleEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { ArticleRO, IPayloadArticleUpdated } from "../../../core/interfaces"; +import { ArticleService } from "../../services"; import { UpdateArticleCommand } from "../impl"; @CommandHandler(UpdateArticleCommand) @@ -18,10 +18,13 @@ export class UpdateArticleCommandHandler constructor( @InjectRepository(ArticleEntity, WRITE_CONNECTION) private readonly articleRepository: Repository, + @Inject(ARTICLE_RMQ_CLIENT) + private readonly articleRmqClient: ClientProxy, - private readonly articleService: ArticleService, - private readonly publisher: PublisherService - ) {} + private readonly articleService: ArticleService + ) { + this.articleRmqClient.connect(); + } async execute({ slug, @@ -33,10 +36,10 @@ export class UpdateArticleCommandHandler const article = await this.articleRepository.save(updated); if (article) { - this.publisher.publish(ARTICLE_QUEUE, { - type: MessageType.ARTICLE_UPDATED, - payload: { article }, - }); + this.articleRmqClient.emit( + { cmd: MessageCmd.ARTICLE_CREATED }, + { article } + ); } return { diff --git a/src/article/commands/impl/create-article.command.ts b/src/article/application/commands/impl/create-article.command.ts similarity index 73% rename from src/article/commands/impl/create-article.command.ts rename to src/article/application/commands/impl/create-article.command.ts index 30073fc..c9493ee 100644 --- a/src/article/commands/impl/create-article.command.ts +++ b/src/article/application/commands/impl/create-article.command.ts @@ -1,4 +1,4 @@ -import { CreateArticleDto } from "../../dto"; +import { CreateArticleDto } from "../../../core/dto"; export class CreateArticleCommand { constructor( diff --git a/src/article/commands/impl/create-comment.command.ts b/src/article/application/commands/impl/create-comment.command.ts similarity index 77% rename from src/article/commands/impl/create-comment.command.ts rename to src/article/application/commands/impl/create-comment.command.ts index a294800..9a0d03f 100644 --- a/src/article/commands/impl/create-comment.command.ts +++ b/src/article/application/commands/impl/create-comment.command.ts @@ -1,4 +1,4 @@ -import { CreateCommentDto } from "../../dto"; +import { CreateCommentDto } from "../../../core/dto"; export class CreateCommentCommand { constructor( diff --git a/src/article/commands/impl/delete-article.command.ts b/src/article/application/commands/impl/delete-article.command.ts similarity index 100% rename from src/article/commands/impl/delete-article.command.ts rename to src/article/application/commands/impl/delete-article.command.ts diff --git a/src/article/commands/impl/delete-comment.command.ts b/src/article/application/commands/impl/delete-comment.command.ts similarity index 100% rename from src/article/commands/impl/delete-comment.command.ts rename to src/article/application/commands/impl/delete-comment.command.ts diff --git a/src/article/commands/impl/favorite-article.command.ts b/src/article/application/commands/impl/favorite-article.command.ts similarity index 100% rename from src/article/commands/impl/favorite-article.command.ts rename to src/article/application/commands/impl/favorite-article.command.ts diff --git a/src/article/commands/impl/index.ts b/src/article/application/commands/impl/index.ts similarity index 100% rename from src/article/commands/impl/index.ts rename to src/article/application/commands/impl/index.ts diff --git a/src/article/commands/impl/unfavorite-article.command.ts b/src/article/application/commands/impl/unfavorite-article.command.ts similarity index 100% rename from src/article/commands/impl/unfavorite-article.command.ts rename to src/article/application/commands/impl/unfavorite-article.command.ts diff --git a/src/article/commands/impl/update-article.command.ts b/src/article/application/commands/impl/update-article.command.ts similarity index 72% rename from src/article/commands/impl/update-article.command.ts rename to src/article/application/commands/impl/update-article.command.ts index aa85b5a..c419478 100644 --- a/src/article/commands/impl/update-article.command.ts +++ b/src/article/application/commands/impl/update-article.command.ts @@ -1,4 +1,4 @@ -import { CreateArticleDto } from "../../dto"; +import { CreateArticleDto } from "../../../core/dto"; export class UpdateArticleCommand { constructor( diff --git a/src/article/commands/index.ts b/src/article/application/commands/index.ts similarity index 100% rename from src/article/commands/index.ts rename to src/article/application/commands/index.ts diff --git a/src/article/events/event.module.ts b/src/article/application/events/event.module.ts similarity index 57% rename from src/article/events/event.module.ts rename to src/article/application/events/event.module.ts index 6a2b041..c79c4d7 100644 --- a/src/article/events/event.module.ts +++ b/src/article/application/events/event.module.ts @@ -1,11 +1,11 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { READ_CONNECTION } from "../../config"; -import { UserEntity } from "../../user/core/entities/user.entity"; -import { ArticleEntity, BlockEntity } from "../core"; -import { CommentEntity } from "../core/entities/comment.entity"; -import { EventHandlers } from "../events"; +import { READ_CONNECTION } from "../../../configs"; +import { UserEntity } from "../../../user/core/entities/user.entity"; +import { ArticleEntity, BlockEntity } from "../../core"; +import { CommentEntity } from "../../core/entities/comment.entity"; +import { EventHandlers } from "."; @Module({ imports: [ diff --git a/src/article/events/handlers/article-created.handler.ts b/src/article/application/events/handlers/article-created.handler.ts similarity index 86% rename from src/article/events/handlers/article-created.handler.ts rename to src/article/application/events/handlers/article-created.handler.ts index 31237a9..629e7be 100644 --- a/src/article/events/handlers/article-created.handler.ts +++ b/src/article/application/events/handlers/article-created.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { ArticleEntity } from "../../core/entities/article.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { ArticleEntity } from "../../../core/entities/article.entity"; import { ArticleCreatedEvent } from "../impl"; @EventsHandler(ArticleCreatedEvent) diff --git a/src/article/events/handlers/article-deleted.handler.ts b/src/article/application/events/handlers/article-deleted.handler.ts similarity index 96% rename from src/article/events/handlers/article-deleted.handler.ts rename to src/article/application/events/handlers/article-deleted.handler.ts index 330ae11..6acf6f2 100644 --- a/src/article/events/handlers/article-deleted.handler.ts +++ b/src/article/application/events/handlers/article-deleted.handler.ts @@ -3,9 +3,9 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; +import { READ_CONNECTION } from "../../../../configs"; import { ArticleDeletedEvent } from "../impl"; -import { CommentEntity, ArticleEntity, BlockEntity } from "../../core"; +import { CommentEntity, ArticleEntity, BlockEntity } from "../../../core"; @EventsHandler(ArticleDeletedEvent) export class ArticleDeletedEventHandler diff --git a/src/article/events/handlers/article-favorited.handler.ts b/src/article/application/events/handlers/article-favorited.handler.ts similarity index 83% rename from src/article/events/handlers/article-favorited.handler.ts rename to src/article/application/events/handlers/article-favorited.handler.ts index a637c90..ea98ec2 100644 --- a/src/article/events/handlers/article-favorited.handler.ts +++ b/src/article/application/events/handlers/article-favorited.handler.ts @@ -3,9 +3,9 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; import { ArticleFavoritedEvent } from "../impl"; @EventsHandler(ArticleFavoritedEvent) diff --git a/src/article/events/handlers/article-unfavorited.handler.ts b/src/article/application/events/handlers/article-unfavorited.handler.ts similarity index 83% rename from src/article/events/handlers/article-unfavorited.handler.ts rename to src/article/application/events/handlers/article-unfavorited.handler.ts index 5812357..cc00f71 100644 --- a/src/article/events/handlers/article-unfavorited.handler.ts +++ b/src/article/application/events/handlers/article-unfavorited.handler.ts @@ -3,9 +3,9 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; import { ArticleUnFavoritedEvent } from "../impl"; @EventsHandler(ArticleUnFavoritedEvent) diff --git a/src/article/events/handlers/article-updated.handler.ts b/src/article/application/events/handlers/article-updated.handler.ts similarity index 89% rename from src/article/events/handlers/article-updated.handler.ts rename to src/article/application/events/handlers/article-updated.handler.ts index 599e055..7b44358 100644 --- a/src/article/events/handlers/article-updated.handler.ts +++ b/src/article/application/events/handlers/article-updated.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { ArticleEntity } from "../../core/entities/article.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { ArticleEntity } from "../../../core/entities/article.entity"; import { ArticleUpdatedEvent } from "../impl"; @EventsHandler(ArticleUpdatedEvent) diff --git a/src/article/events/handlers/comment-created.handler.ts b/src/article/application/events/handlers/comment-created.handler.ts similarity index 86% rename from src/article/events/handlers/comment-created.handler.ts rename to src/article/application/events/handlers/comment-created.handler.ts index e5c5620..bf99a04 100644 --- a/src/article/events/handlers/comment-created.handler.ts +++ b/src/article/application/events/handlers/comment-created.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { CommentEntity } from "../../core/entities/comment.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { CommentEntity } from "../../../core/entities/comment.entity"; import { CommentCreatedEvent } from "../impl"; @EventsHandler(CommentCreatedEvent) diff --git a/src/article/events/handlers/comment-deleted.handler.ts b/src/article/application/events/handlers/comment-deleted.handler.ts similarity index 86% rename from src/article/events/handlers/comment-deleted.handler.ts rename to src/article/application/events/handlers/comment-deleted.handler.ts index c068d14..0d82464 100644 --- a/src/article/events/handlers/comment-deleted.handler.ts +++ b/src/article/application/events/handlers/comment-deleted.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { CommentEntity } from "../../core/entities/comment.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { CommentEntity } from "../../../core/entities/comment.entity"; import { CommentDeletedEvent } from "../impl"; @EventsHandler(CommentDeletedEvent) diff --git a/src/article/events/handlers/index.ts b/src/article/application/events/handlers/index.ts similarity index 100% rename from src/article/events/handlers/index.ts rename to src/article/application/events/handlers/index.ts diff --git a/src/article/events/impl/article-created.event.ts b/src/article/application/events/impl/article-created.event.ts similarity index 57% rename from src/article/events/impl/article-created.event.ts rename to src/article/application/events/impl/article-created.event.ts index 042a9ac..8a2aba7 100644 --- a/src/article/events/impl/article-created.event.ts +++ b/src/article/application/events/impl/article-created.event.ts @@ -1,4 +1,4 @@ -import { ArticleEntity } from "../../core/entities/article.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; export class ArticleCreatedEvent { constructor(public readonly article: ArticleEntity) {} diff --git a/src/article/events/impl/article-deleted.event.ts b/src/article/application/events/impl/article-deleted.event.ts similarity index 100% rename from src/article/events/impl/article-deleted.event.ts rename to src/article/application/events/impl/article-deleted.event.ts diff --git a/src/article/application/events/impl/article-favorited.event.ts b/src/article/application/events/impl/article-favorited.event.ts new file mode 100644 index 0000000..559ea8b --- /dev/null +++ b/src/article/application/events/impl/article-favorited.event.ts @@ -0,0 +1,9 @@ +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; + +export class ArticleFavoritedEvent { + constructor( + public readonly user: UserEntity, + public readonly article: ArticleEntity + ) {} +} diff --git a/src/article/events/impl/article-unfavorited.event.ts b/src/article/application/events/impl/article-unfavorited.event.ts similarity index 50% rename from src/article/events/impl/article-unfavorited.event.ts rename to src/article/application/events/impl/article-unfavorited.event.ts index 75dfd66..25460f7 100644 --- a/src/article/events/impl/article-unfavorited.event.ts +++ b/src/article/application/events/impl/article-unfavorited.event.ts @@ -1,5 +1,5 @@ -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; export class ArticleUnFavoritedEvent { constructor( diff --git a/src/article/events/impl/article-updated.event.ts b/src/article/application/events/impl/article-updated.event.ts similarity index 57% rename from src/article/events/impl/article-updated.event.ts rename to src/article/application/events/impl/article-updated.event.ts index 35fe7a7..e03e0fb 100644 --- a/src/article/events/impl/article-updated.event.ts +++ b/src/article/application/events/impl/article-updated.event.ts @@ -1,4 +1,4 @@ -import { ArticleEntity } from "../../core/entities/article.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; export class ArticleUpdatedEvent { constructor(public readonly article: ArticleEntity) {} diff --git a/src/article/events/impl/comment-created.event.ts b/src/article/application/events/impl/comment-created.event.ts similarity index 78% rename from src/article/events/impl/comment-created.event.ts rename to src/article/application/events/impl/comment-created.event.ts index 8763d05..c3557e4 100644 --- a/src/article/events/impl/comment-created.event.ts +++ b/src/article/application/events/impl/comment-created.event.ts @@ -1,6 +1,6 @@ // import { Comment } from "../../core/entities/comment.entity"; -import { IComment } from "../../core"; +import { IComment } from "../../../core"; export class CommentCreatedEvent { constructor(public readonly comment: IComment) {} diff --git a/src/article/events/impl/comment-deleted.event.ts b/src/article/application/events/impl/comment-deleted.event.ts similarity index 78% rename from src/article/events/impl/comment-deleted.event.ts rename to src/article/application/events/impl/comment-deleted.event.ts index 3883bb5..a51cef2 100644 --- a/src/article/events/impl/comment-deleted.event.ts +++ b/src/article/application/events/impl/comment-deleted.event.ts @@ -1,6 +1,6 @@ // import { Comment } from "../../core/entities/comment.entity"; -import { IComment } from "../../core"; +import { IComment } from "../../../core"; export class CommentDeletedEvent { constructor(public readonly comment: IComment) {} diff --git a/src/article/events/impl/index.ts b/src/article/application/events/impl/index.ts similarity index 100% rename from src/article/events/impl/index.ts rename to src/article/application/events/impl/index.ts diff --git a/src/article/events/index.ts b/src/article/application/events/index.ts similarity index 100% rename from src/article/events/index.ts rename to src/article/application/events/index.ts diff --git a/src/article/queries/handlers/find-all.article.handler.ts b/src/article/application/queries/handlers/find-all.article.handler.ts similarity index 90% rename from src/article/queries/handlers/find-all.article.handler.ts rename to src/article/application/queries/handlers/find-all.article.handler.ts index 74c332e..fd58228 100644 --- a/src/article/queries/handlers/find-all.article.handler.ts +++ b/src/article/application/queries/handlers/find-all.article.handler.ts @@ -2,11 +2,11 @@ import { HttpException, HttpStatus } from "@nestjs/common"; import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { getRepository, Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { FollowsEntity } from "../../../profile/core/entities/follows.entity"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { ArticlesRO } from "../../core/interfaces/article.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { FollowsEntity } from "../../../../profile/core/entities/follows.entity"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; +import { ArticlesRO } from "../../../core/interfaces/article.interface"; import { ArticleService } from "../../services/article.service"; import { FindAllArticleQuery } from "../impl"; diff --git a/src/article/queries/handlers/find-comments.handler.ts b/src/article/application/queries/handlers/find-comments.handler.ts similarity index 83% rename from src/article/queries/handlers/find-comments.handler.ts rename to src/article/application/queries/handlers/find-comments.handler.ts index 20e7550..bba788e 100644 --- a/src/article/queries/handlers/find-comments.handler.ts +++ b/src/article/application/queries/handlers/find-comments.handler.ts @@ -1,9 +1,9 @@ import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { CommentsRO } from "../../core/interfaces/article.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { ArticleEntity } from "../../../core/entities/article.entity"; +import { CommentsRO } from "../../../core/interfaces/article.interface"; import { ArticleService } from "../../services/article.service"; import { FindCommentQuery } from "../impl"; diff --git a/src/article/queries/handlers/find-feed-article.handler.ts b/src/article/application/queries/handlers/find-feed-article.handler.ts similarity index 79% rename from src/article/queries/handlers/find-feed-article.handler.ts rename to src/article/application/queries/handlers/find-feed-article.handler.ts index 5647283..38f03e0 100644 --- a/src/article/queries/handlers/find-feed-article.handler.ts +++ b/src/article/application/queries/handlers/find-feed-article.handler.ts @@ -1,11 +1,11 @@ import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { getRepository, Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { FollowsEntity } from "../../../profile/core/entities/follows.entity"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { ArticlesRO } from "../../core/interfaces/article.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { FollowsEntity } from "../../../../profile/core/entities/follows.entity"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; +import { ArticlesRO } from "../../../core/interfaces/article.interface"; import { ArticleService } from "../../services/article.service"; import { FindFeedArticleQuery } from "../impl"; @@ -18,6 +18,8 @@ export class FindFeedArticleQueryHandler private readonly userRepository: Repository, @InjectRepository(FollowsEntity, READ_CONNECTION) private readonly followsRepository: Repository, + @InjectRepository(ArticleEntity, READ_CONNECTION) + private readonly articleRepository: Repository, private readonly articleService: ArticleService ) {} @@ -35,7 +37,7 @@ export class FindFeedArticleQueryHandler const ids = _follows.map((el) => el.followingId); - const qb = getRepository(ArticleEntity, READ_CONNECTION) + const qb = this.articleRepository .createQueryBuilder("article") .where("article.authorId IN (:...ids)", { ids }) .leftJoinAndSelect("article.author", "author"); diff --git a/src/article/queries/handlers/find-one-article.handler.ts b/src/article/application/queries/handlers/find-one-article.handler.ts similarity index 81% rename from src/article/queries/handlers/find-one-article.handler.ts rename to src/article/application/queries/handlers/find-one-article.handler.ts index 963bfd5..2c9dd0b 100644 --- a/src/article/queries/handlers/find-one-article.handler.ts +++ b/src/article/application/queries/handlers/find-one-article.handler.ts @@ -2,11 +2,11 @@ import { HttpException, HttpStatus } from "@nestjs/common"; import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { FollowsEntity } from "../../../profile/core/entities/follows.entity"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; -import { ArticleRO } from "../../core/interfaces/article.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { FollowsEntity } from "../../../../profile/core/entities/follows.entity"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { ArticleEntity } from "../../../core/entities/article.entity"; +import { ArticleRO } from "../../../core/interfaces/article.interface"; import { ArticleService } from "../../services/article.service"; import { FindOneArticleQuery } from "../impl"; diff --git a/src/article/queries/handlers/index.ts b/src/article/application/queries/handlers/index.ts similarity index 100% rename from src/article/queries/handlers/index.ts rename to src/article/application/queries/handlers/index.ts diff --git a/src/article/queries/impl/find-all.article.query.ts b/src/article/application/queries/impl/find-all.article.query.ts similarity index 67% rename from src/article/queries/impl/find-all.article.query.ts rename to src/article/application/queries/impl/find-all.article.query.ts index 9aa0d44..48842e3 100644 --- a/src/article/queries/impl/find-all.article.query.ts +++ b/src/article/application/queries/impl/find-all.article.query.ts @@ -1,4 +1,4 @@ -import { ArticleFilters } from "../../dto/article-query"; +import { ArticleFilters } from "../../../core/dto/article-query"; export class FindAllArticleQuery { constructor( diff --git a/src/article/queries/impl/find-comments.query.ts b/src/article/application/queries/impl/find-comments.query.ts similarity index 100% rename from src/article/queries/impl/find-comments.query.ts rename to src/article/application/queries/impl/find-comments.query.ts diff --git a/src/article/queries/impl/find-feed-article.query.ts b/src/article/application/queries/impl/find-feed-article.query.ts similarity index 67% rename from src/article/queries/impl/find-feed-article.query.ts rename to src/article/application/queries/impl/find-feed-article.query.ts index b4f55bc..b2fbd77 100644 --- a/src/article/queries/impl/find-feed-article.query.ts +++ b/src/article/application/queries/impl/find-feed-article.query.ts @@ -1,4 +1,4 @@ -import { ArticleFilters } from "../../dto/article-query"; +import { ArticleFilters } from "../../../core/dto/article-query"; export class FindFeedArticleQuery { constructor( diff --git a/src/article/queries/impl/find-one-article.query.ts b/src/article/application/queries/impl/find-one-article.query.ts similarity index 100% rename from src/article/queries/impl/find-one-article.query.ts rename to src/article/application/queries/impl/find-one-article.query.ts diff --git a/src/article/queries/impl/index.ts b/src/article/application/queries/impl/index.ts similarity index 100% rename from src/article/queries/impl/index.ts rename to src/article/application/queries/impl/index.ts diff --git a/src/article/queries/index.ts b/src/article/application/queries/index.ts similarity index 100% rename from src/article/queries/index.ts rename to src/article/application/queries/index.ts diff --git a/src/article/queries/query.module.ts b/src/article/application/queries/query.module.ts similarity index 58% rename from src/article/queries/query.module.ts rename to src/article/application/queries/query.module.ts index 3785a4b..3f62b2a 100644 --- a/src/article/queries/query.module.ts +++ b/src/article/application/queries/query.module.ts @@ -1,13 +1,14 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { QueryHandlers } from "."; -import { READ_CONNECTION } from "../../config"; -import { FollowsEntity } from "../../profile/core/entities/follows.entity"; -import { UserEntity } from "../../user/core/entities/user.entity"; -import { UserModule } from "../../user/user.module"; -import { ArticleEntity } from "../core"; -import { CommentEntity } from "../core/entities/comment.entity"; +import { READ_CONNECTION } from "../../../configs"; +import { FollowsEntity } from "../../../profile/core/entities/follows.entity"; +import { UserEntity } from "../../../user/core/entities/user.entity"; +import { UserModule } from "../../../user/user.module"; +import { ArticleEntity } from "../../core"; +import { CommentEntity } from "../../core/entities/comment.entity"; import { ArticleService } from "../services/article.service"; @Module({ diff --git a/src/article/services/article.service.ts b/src/article/application/services/article.service.ts similarity index 96% rename from src/article/services/article.service.ts rename to src/article/application/services/article.service.ts index cf10d5b..eb153b3 100644 --- a/src/article/services/article.service.ts +++ b/src/article/application/services/article.service.ts @@ -1,12 +1,12 @@ import { Injectable } from "@nestjs/common"; -import { UserEntity } from "../../user/core/entities/user.entity"; +import { UserEntity } from "../../../user/core/entities/user.entity"; import { ArticleData, IComment, ArticleEntity, BlockEntity, CommentEntity, -} from "../core"; +} from "../../core"; const slug = require("slug"); @Injectable() diff --git a/src/article/application/services/index.ts b/src/article/application/services/index.ts new file mode 100644 index 0000000..258dc29 --- /dev/null +++ b/src/article/application/services/index.ts @@ -0,0 +1 @@ +export * from "./article.service"; diff --git a/src/article/application/use-cases/find-all.article.use-case.ts b/src/article/application/use-cases/find-all.article.use-case.ts new file mode 100644 index 0000000..5ef1142 --- /dev/null +++ b/src/article/application/use-cases/find-all.article.use-case.ts @@ -0,0 +1,61 @@ +import { Injectable } from "@nestjs/common"; +import { QueryBus } from "@nestjs/cqrs"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository, getRepository } from "typeorm"; + +import { READ_CONNECTION } from "../../../configs"; +import { FollowsEntity } from "../../../profile/core"; +import { UserEntity } from "../../../user/core"; +import { ArticleEntity, ArticlesRO } from "../../core"; +import { ArticleFilters } from "../../core/dto"; +import { FindAllArticleQuery } from "../queries"; +import { ArticleService } from "../services"; + +@Injectable() +export class FindAllArticleUseCase { + constructor( + @InjectRepository(UserEntity, READ_CONNECTION) + private readonly userRepository: Repository, + + private readonly articleService: ArticleService, + private readonly queryBus: QueryBus + ) {} + + async execute(userId: number, query: ArticleFilters): Promise { + // const { articles, articlesCount } = await this.queryBus.execute( + // new FindAllArticleQuery(query) + // ); + // let user = null; + // let follows = []; + // if (userId) { + // const authorIds = articles + // .map((art: ArticleEntity) => art.author.id) + // .filter((id: number, index: number, ids) => ids.indexOf(id) === index); + // user = await this.userRepository.findOne(userId, { + // relations: ["favorites"], + // }); + // const followsBuilder = getRepository(FollowsEntity, READ_CONNECTION) + // .createQueryBuilder("follows") + // .where("follows.followerId = :followerId", { followerId: userId }); + // if (authorIds.length > 0) { + // followsBuilder.andWhere("follows.followingId IN (:...followingIds)", { + // followingIds: authorIds, + // }); + // } + // follows = await followsBuilder.getMany(); + // } + // const articlesRO = articles?.map((article) => { + // const following = + // follows?.filter((follow) => follow.followingId === article.author.id) + // .length > 0; + // return this.articleService.buildArticleRO(article, user, following); + // }); + // return { + // // articles: articlesRO, + // articles, + // articlesCount, + // user, + // follows, + // }; + } +} diff --git a/src/article/application/use-cases/find-one.article.use-case.ts b/src/article/application/use-cases/find-one.article.use-case.ts new file mode 100644 index 0000000..c5557b5 --- /dev/null +++ b/src/article/application/use-cases/find-one.article.use-case.ts @@ -0,0 +1,30 @@ +import { Injectable } from "@nestjs/common"; +import { QueryBus } from "@nestjs/cqrs"; + +import { ArticleRO } from "../../core"; +import { FindOneArticleQuery } from "../queries"; +import { ArticleService } from "../services"; + +@Injectable() +export class FindOneArticleUseCase { + constructor( + private readonly articleService: ArticleService, + private readonly queryBus: QueryBus + ) {} + + async execute(userId: number, slug: string): Promise { + const { article, user, follows } = await this.queryBus.execute( + new FindOneArticleQuery(userId, slug) + ); + + const articleData = this.articleService.buildArticleRO( + article, + user, + !!follows + ); + + return { + article: articleData, + }; + } +} diff --git a/src/article/application/use-cases/index.ts b/src/article/application/use-cases/index.ts new file mode 100644 index 0000000..bd50923 --- /dev/null +++ b/src/article/application/use-cases/index.ts @@ -0,0 +1,7 @@ +import { FindAllArticleUseCase } from "./find-all.article.use-case"; +import { FindOneArticleUseCase } from "./find-one.article.use-case"; + +export * from "./find-all.article.use-case"; +export * from "./find-one.article.use-case"; + +export const UseCases = [FindAllArticleUseCase, FindOneArticleUseCase]; diff --git a/src/article/application/use-cases/use-case.module.ts b/src/article/application/use-cases/use-case.module.ts new file mode 100644 index 0000000..a520ad2 --- /dev/null +++ b/src/article/application/use-cases/use-case.module.ts @@ -0,0 +1,27 @@ +import { Module } from "@nestjs/common"; +import { CqrsModule } from "@nestjs/cqrs"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { UseCases } from "./index"; +import { READ_CONNECTION } from "../../../configs"; +import { FollowsEntity } from "../../../profile/core"; +import { UserEntity } from "../../../user/core"; +import { UserModule } from "../../../user/user.module"; +import { ArticleEntity, CommentEntity } from "../../core"; +import { QueryModule } from "../queries/query.module"; +import { ArticleService } from "../services"; + +@Module({ + imports: [ + TypeOrmModule.forFeature( + [ArticleEntity, CommentEntity, UserEntity, FollowsEntity], + READ_CONNECTION + ), + UserModule, + QueryModule, + CqrsModule, + ], + providers: [ArticleService, ...UseCases], + exports: [...UseCases], +}) +export class UseCaseModule {} diff --git a/src/article/article.module.ts b/src/article/article.module.ts index 0b16e8b..ba46ca5 100644 --- a/src/article/article.module.ts +++ b/src/article/article.module.ts @@ -5,16 +5,18 @@ import { RequestMethod, } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; + import { RabbitMqModule } from "../rabbitmq/rabbitmq.module"; -import { AuthMiddleware } from "../user/auth.middleware"; -import { UserModule } from "../user/user.module"; -import { ArticleController } from "./article.controller"; -import { ArticleProjection } from "./article.projection"; -import { ArticleService } from "./services/article.service"; -import { CommandModule } from "./commands/command.module"; -import { EventModule } from "./events/event.module"; -import { QueryModule } from "./queries/query.module"; import { RedisModule } from "../redis/redis.module"; +import { AuthMiddleware } from "../shared/middleware/auth.middleware"; +import { UserModule } from "../user/user.module"; +import { CommandModule } from "./application/commands/command.module"; +import { EventModule } from "./application/events/event.module"; +import { QueryModule } from "./application/queries/query.module"; +import { ArticleService } from "./application/services"; +import { UseCaseModule } from "./application/use-cases/use-case.module"; +import { ArticleController } from "./presentation/rest"; +import { ArticleRmq } from "./presentation/rmq"; @Module({ imports: [ @@ -23,11 +25,12 @@ import { RedisModule } from "../redis/redis.module"; CommandModule, QueryModule, EventModule, + UseCaseModule, RabbitMqModule, RedisModule, ], - providers: [ArticleService, ArticleProjection], - controllers: [ArticleController], + providers: [ArticleService], + controllers: [ArticleController, ArticleRmq], }) export class ArticleModule implements NestModule { public configure(consumer: MiddlewareConsumer) { diff --git a/src/article/article.projection.ts b/src/article/article.projection.ts deleted file mode 100644 index c981fcc..0000000 --- a/src/article/article.projection.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { EventBus } from "@nestjs/cqrs"; -import { ConsumerService } from "../rabbitmq/consumer.service"; -import { ARTICLE_QUEUE } from "../rabbitmq/rabbitmq.constants"; -import { IMessage, IProjection } from "./core"; -import { MessageType } from "./core/enums/article.enum"; -import { - ArticleCreatedEvent, - ArticleDeletedEvent, - ArticleFavoritedEvent, - ArticleUnFavoritedEvent, - ArticleUpdatedEvent, - CommentCreatedEvent, - CommentDeletedEvent, -} from "./events"; - -@Injectable() -export class ArticleProjection implements IProjection { - constructor( - private readonly consumer: ConsumerService, - private readonly eventBus: EventBus - ) {} - - async handle() { - await this.consumer.consume(ARTICLE_QUEUE, (msg: IMessage) => { - this.handleMessage(msg); - }); - } - - private async handleMessage({ type, payload }: IMessage) { - switch (type) { - case MessageType.ARTICLE_CREATED: - this.eventBus.publish(new ArticleCreatedEvent(payload.article)); - break; - case MessageType.ARTICLE_UPDATED: - this.eventBus.publish(new ArticleUpdatedEvent(payload.article)); - break; - case MessageType.ARTICLE_DELETED: - this.eventBus.publish( - new ArticleDeletedEvent(payload.userId, payload.slug) - ); - break; - case MessageType.ARTICLE_FAVORITED: - this.eventBus.publish( - new ArticleFavoritedEvent(payload.user, payload.article) - ); - break; - case MessageType.ARTICLE_UNFAVORITED: - this.eventBus.publish( - new ArticleUnFavoritedEvent(payload.user, payload.article) - ); - break; - case MessageType.COMMENT_CREATED: - this.eventBus.publish(new CommentCreatedEvent(payload.comment)); - break; - case MessageType.COMMENT_DELETED: - this.eventBus.publish(new CommentDeletedEvent(payload.comment)); - break; - default: - break; - } - } -} diff --git a/src/article/dto/article-query.ts b/src/article/core/dto/article-query.ts similarity index 100% rename from src/article/dto/article-query.ts rename to src/article/core/dto/article-query.ts diff --git a/src/article/dto/block.dto.ts b/src/article/core/dto/block.dto.ts similarity index 91% rename from src/article/dto/block.dto.ts rename to src/article/core/dto/block.dto.ts index 443e6c9..f5b2203 100644 --- a/src/article/dto/block.dto.ts +++ b/src/article/core/dto/block.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { BlockType } from "../core/enums/block.enum"; +import { BlockType } from "../enums/block.enum"; class Info { @ApiProperty() diff --git a/src/article/dto/create-article.dto.ts b/src/article/core/dto/create-article.dto.ts similarity index 100% rename from src/article/dto/create-article.dto.ts rename to src/article/core/dto/create-article.dto.ts diff --git a/src/article/dto/create-comment.ts b/src/article/core/dto/create-comment.ts similarity index 100% rename from src/article/dto/create-comment.ts rename to src/article/core/dto/create-comment.ts diff --git a/src/article/dto/index.ts b/src/article/core/dto/index.ts similarity index 100% rename from src/article/dto/index.ts rename to src/article/core/dto/index.ts diff --git a/src/article/core/enums/article.enum.ts b/src/article/core/enums/article.enum.ts index 82e977e..3aa915e 100644 --- a/src/article/core/enums/article.enum.ts +++ b/src/article/core/enums/article.enum.ts @@ -1,4 +1,4 @@ -export enum MessageType { +export enum MessageCmd { ARTICLE_CREATED = "ARTICLE_CREATED", ARTICLE_UPDATED = "ARTICLE_UPDATED", ARTICLE_DELETED = "ARTICLE_DELETED", diff --git a/src/article/core/interfaces/article.interface.ts b/src/article/core/interfaces/article.interface.ts index 0d0cac3..b8febe0 100644 --- a/src/article/core/interfaces/article.interface.ts +++ b/src/article/core/interfaces/article.interface.ts @@ -1,7 +1,10 @@ -import { BlockEntity } from "../entities/block.entity"; -import { IBlock } from "./block.interface"; +import { FollowsEntity } from "../../../profile/core"; import { ProfileData } from "../../../profile/core/interfaces/profile.interface"; +import { UserEntity } from "../../../user/core"; import { IUser } from "../../../user/core/interfaces/user.interface"; +import { ArticleEntity } from "../entities"; +import { BlockEntity } from "../entities/block.entity"; +import { IBlock } from "./block.interface"; // export interface Comment { // id: number; @@ -41,6 +44,17 @@ export interface ArticlesRO { articlesCount: number; } +export interface ArticlesResponse { + articles: ArticleEntity[]; + articlesCount: number; +} + +export interface ArticleResponse { + article: ArticleEntity; + user?: UserEntity; + follows?: FollowsEntity; +} + export interface IArticle { id?: number; slug?: string; @@ -63,3 +77,25 @@ export interface IComment { article?: IArticle; author?: IUser | ProfileData; } + +interface IPayloadArticleRmq { + article: ArticleEntity; +} +export interface IPayloadArticleCreated extends IPayloadArticleRmq {} +export interface IPayloadArticleUpdated extends IPayloadArticleRmq {} +export interface IPayloadArticleDeleted { + userId: number; + slug: string; +} +export interface IPayloadArticleFavorited extends IPayloadArticleRmq { + user: UserEntity; +} +export interface IPayloadArticleUnFavorited extends IPayloadArticleRmq { + user: UserEntity; +} +export interface IPayloadCommentCreated { + comment: IComment; +} +export interface IPayloadCommentDeleted { + comment: IComment; +} diff --git a/src/article/core/interfaces/index.ts b/src/article/core/interfaces/index.ts index 2833b59..9a5f693 100644 --- a/src/article/core/interfaces/index.ts +++ b/src/article/core/interfaces/index.ts @@ -1,3 +1,2 @@ export * from "./article.interface"; export * from "./block.interface"; -export * from "./projection.interface"; diff --git a/src/article/core/interfaces/projection.interface.ts b/src/article/core/interfaces/projection.interface.ts deleted file mode 100644 index d9468d4..0000000 --- a/src/article/core/interfaces/projection.interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../entities/article.entity"; -import { MessageType } from "../enums"; -import { IComment } from "./article.interface"; - -export interface IMessage { - type: MessageType; - payload: { - article?: ArticleEntity; - user?: UserEntity; - slug?: string; - userId?: number; - comment?: IComment; - }; -} - -export interface IProjection { - handle(): Promise; -} diff --git a/src/article/events/impl/article-favorited.event.ts b/src/article/events/impl/article-favorited.event.ts deleted file mode 100644 index b9fc346..0000000 --- a/src/article/events/impl/article-favorited.event.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { ArticleEntity } from "../../core/entities/article.entity"; - -export class ArticleFavoritedEvent { - constructor( - public readonly user: UserEntity, - public readonly article: ArticleEntity - ) {} -} diff --git a/src/article/presentation/interceptors/index.ts b/src/article/presentation/interceptors/index.ts new file mode 100644 index 0000000..f428975 --- /dev/null +++ b/src/article/presentation/interceptors/index.ts @@ -0,0 +1 @@ +export * from "./transform-interceptor"; diff --git a/src/article/presentation/interceptors/transform-interceptor.ts b/src/article/presentation/interceptors/transform-interceptor.ts new file mode 100644 index 0000000..950aa15 --- /dev/null +++ b/src/article/presentation/interceptors/transform-interceptor.ts @@ -0,0 +1,51 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + Logger, +} from "@nestjs/common"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; +import { ArticleEntity, ArticleRO } from "../../core"; +import { ArticleService } from "../../application/services"; +import { UserEntity } from "../../../user/core"; +import { FollowsEntity } from "../../../profile/core"; + +@Injectable() +export class TransformInterceptor implements NestInterceptor { + constructor(private readonly articleService: ArticleService) {} + + intercept( + context: ExecutionContext, + next: CallHandler + ): Observable { + return next.handle().pipe( + map( + ({ + article, + user = undefined, + following = undefined, + }: { + article: ArticleEntity; + user?: UserEntity; + following?: boolean; + }) => { + console.log("=================After================="); + Logger.warn({ + article, + user, + following, + }); + return { + article: this.articleService.buildArticleRO( + article, + user, + following + ), + }; + } + ) + ); + } +} diff --git a/src/article/article.controller.ts b/src/article/presentation/rest/article.controller.ts similarity index 94% rename from src/article/article.controller.ts rename to src/article/presentation/rest/article.controller.ts index 0499bef..c9c3064 100644 --- a/src/article/article.controller.ts +++ b/src/article/presentation/rest/article.controller.ts @@ -17,7 +17,7 @@ import { ApiResponse, ApiTags, } from "@nestjs/swagger"; -import { User } from "../user/user.decorator"; +import { User } from "../../../shared/middleware/user.decorator"; import { CreateArticleCommand, CreateCommentCommand, @@ -26,15 +26,19 @@ import { FavoriteArticleCommand, UnFavoriteArticleCommand, UpdateArticleCommand, -} from "./commands"; -import { ArticleRO, ArticlesRO, CommentsRO } from "./core"; -import { ArticleFilters, CreateArticleDto, CreateCommentDto } from "./dto"; +} from "../../application/commands"; +import { ArticleRO, ArticlesRO, CommentsRO } from "../../core"; +import { + ArticleFilters, + CreateArticleDto, + CreateCommentDto, +} from "../../core/dto"; import { FindAllArticleQuery, FindCommentQuery, FindFeedArticleQuery, FindOneArticleQuery, -} from "./queries"; +} from "../../application/queries"; @ApiBearerAuth() @ApiTags("articles") @@ -75,7 +79,7 @@ export class ArticleController { @Get(":slug") async findOne( @User("id") userId: number, - @Param("slug") slug + @Param("slug") slug: string ): Promise { return this.queryBus.execute(new FindOneArticleQuery(userId, slug)); } diff --git a/src/article/presentation/rest/index.ts b/src/article/presentation/rest/index.ts new file mode 100644 index 0000000..9a4045f --- /dev/null +++ b/src/article/presentation/rest/index.ts @@ -0,0 +1 @@ +export * from "./article.controller"; diff --git a/src/article/presentation/rmq/article.rmq.ts b/src/article/presentation/rmq/article.rmq.ts new file mode 100644 index 0000000..2301c43 --- /dev/null +++ b/src/article/presentation/rmq/article.rmq.ts @@ -0,0 +1,67 @@ +import { Controller } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; +import { EventPattern, Payload, Transport } from "@nestjs/microservices"; + +import { + ArticleCreatedEvent, + ArticleDeletedEvent, + ArticleFavoritedEvent, + ArticleUnFavoritedEvent, + ArticleUpdatedEvent, + CommentCreatedEvent, + CommentDeletedEvent, +} from "../../application/events"; +import { + IPayloadArticleCreated, + IPayloadArticleDeleted, + IPayloadArticleFavorited, + IPayloadArticleUnFavorited, + IPayloadArticleUpdated, + IPayloadCommentCreated, + IPayloadCommentDeleted, +} from "../../core"; +import { MessageCmd } from "../../core/enums"; + +@Controller() +export class ArticleRmq { + constructor(private readonly eventBus: EventBus) {} + + @EventPattern({ cmd: MessageCmd.ARTICLE_CREATED }, Transport.RMQ) + async articleCreated(@Payload() payload: IPayloadArticleCreated) { + return this.eventBus.publish(new ArticleCreatedEvent(payload.article)); + } + + @EventPattern({ cmd: MessageCmd.ARTICLE_UPDATED }, Transport.RMQ) + async articleUpdated(@Payload() payload: IPayloadArticleUpdated) { + return this.eventBus.publish(new ArticleUpdatedEvent(payload.article)); + } + + @EventPattern({ cmd: MessageCmd.ARTICLE_DELETED }, Transport.RMQ) + async articleDeleted(@Payload() { userId, slug }: IPayloadArticleDeleted) { + return this.eventBus.publish(new ArticleDeletedEvent(userId, slug)); + } + + @EventPattern({ cmd: MessageCmd.ARTICLE_FAVORITED }, Transport.RMQ) + async articleFavorited(@Payload() payload: IPayloadArticleFavorited) { + return this.eventBus.publish( + new ArticleFavoritedEvent(payload.user, payload.article) + ); + } + + @EventPattern({ cmd: MessageCmd.ARTICLE_UNFAVORITED }, Transport.RMQ) + async articleUnFavorited(@Payload() payload: IPayloadArticleUnFavorited) { + return this.eventBus.publish( + new ArticleUnFavoritedEvent(payload.user, payload.article) + ); + } + + @EventPattern({ cmd: MessageCmd.COMMENT_CREATED }, Transport.RMQ) + async commentCreated(@Payload() payload: IPayloadCommentCreated) { + return this.eventBus.publish(new CommentCreatedEvent(payload.comment)); + } + + @EventPattern({ cmd: MessageCmd.COMMENT_DELETED }, Transport.RMQ) + async commentDeleted(@Payload() payload: IPayloadCommentDeleted) { + return this.eventBus.publish(new CommentDeletedEvent(payload.comment)); + } +} diff --git a/src/article/presentation/rmq/index.ts b/src/article/presentation/rmq/index.ts new file mode 100644 index 0000000..816a3e0 --- /dev/null +++ b/src/article/presentation/rmq/index.ts @@ -0,0 +1 @@ +export * from "./article.rmq"; diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index a1d66e8..0000000 --- a/src/config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const SECRET = "jwt-secret-key"; - -export const WRITE_CONNECTION = "master-db"; -export const READ_CONNECTION = "slave-db"; diff --git a/src/rabbitmq/rabbitmq.constants.ts b/src/configs/index.ts similarity index 68% rename from src/rabbitmq/rabbitmq.constants.ts rename to src/configs/index.ts index 63cf24c..85125ae 100644 --- a/src/rabbitmq/rabbitmq.constants.ts +++ b/src/configs/index.ts @@ -1,3 +1,8 @@ +export const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY; + +export const WRITE_CONNECTION = process.env.WRITE_CONNECTION; +export const READ_CONNECTION = process.env.READ_CONNECTION; + export const RABBIT_MQ_CONNECTION = "amqp://localhost:15672/"; // main @@ -21,3 +26,7 @@ export const PROFILE_DL_ROUTE_KEY = "PROFILE_DL_ROUTE_KEY"; export const ARTICLE_DLQ = "ARTICLE_DLQ"; export const USER_DLQ = "USER_DLQ"; export const PROFILE_DLQ = "PROFILE_DLQ"; + +export const ARTICLE_RMQ_CLIENT = "ARTICLE_RMQ_CLIENT"; +export const USER_RMQ_CLIENT = "USER_RMQ_CLIENT"; +export const PROFILE_RMQ_CLIENT = "PROFILE_RMQ_CLIENT"; diff --git a/src/database/database.module.ts b/src/database/database.module.ts new file mode 100644 index 0000000..a4d5393 --- /dev/null +++ b/src/database/database.module.ts @@ -0,0 +1,49 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import * as dotenv from "dotenv"; + +import { READ_CONNECTION, WRITE_CONNECTION } from "../configs"; +import { join } from "path"; + +dotenv.config(); + +const defaultOptions = { + type: process.env.DATABASE_ENGINE, + host: process.env.DATABASE_HOST, + port: parseInt(process.env.DATABASE_PORT), + username: process.env.DATABASE_USERNAME, + password: process.env.DATABASE_PASSWORD, + entities: [join(__dirname, "../", "/**/core/entities/**.entity{.ts,.js}")], + migrations: [join(__dirname, "../", "/database/migrations/**{.ts,.js}")], + logging: process.env.TYPEORM_LOGGING === "true", + synchronize: process.env.TYPEORM_SYNCHRONIZE === "true", + migrationsRun: process.env.TYPEORM_MIGRATION_RUN === "true", + migrationsTableName: "migrations", + cli: { + migrationsDir: process.env.TYPEORM_MIGRATIONS_DIR, + }, +}; + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + name: WRITE_CONNECTION, + useFactory: () => ({ + ...defaultOptions, + type: "postgres", + database: process.env.WRITE_DATABASE_NAME, + }), + }), + TypeOrmModule.forRootAsync({ + name: READ_CONNECTION, + useFactory: () => ({ + ...defaultOptions, + type: "postgres", + database: process.env.READ_DATABASE_NAME, + }), + }), + ], + controllers: [], + providers: [], +}) +export class DatabaseModule {} diff --git a/src/main.ts b/src/main.ts index 38de3e9..3b30481 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,24 +1,13 @@ +import { NestApplicationOptions } from "@nestjs/common"; import { NestFactory } from "@nestjs/core"; -import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; -import { json, urlencoded } from "express"; -import { INestApplication } from "@nestjs/common"; +import { RmqOptions, Transport } from "@nestjs/microservices"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; import * as cluster from "cluster"; +import { json, urlencoded } from "express"; import * as os from "os"; import { ApplicationModule } from "./app.module"; -import { ArticleProjection } from "./article/article.projection"; -import { UserProjection } from "./user/user.projection"; -import { ProfileProjection } from "./profile/profile.projection"; - -async function executeProjection(app: INestApplication) { - const articleProjection = app.get(ArticleProjection); - const userProjection = app.get(UserProjection); - const profileProjection = app.get(ProfileProjection); - - await articleProjection.handle(); - await userProjection.handle(); - await profileProjection.handle(); -} +import { ARTICLE_QUEUE, PROFILE_QUEUE, USER_QUEUE } from "./configs"; async function bootstrap() { if (cluster.isMaster) { @@ -41,13 +30,46 @@ async function bootstrap() { cluster.fork(); }); } else { - const appOptions = { cors: true }; + const appOptions: NestApplicationOptions = { + cors: { + origin: [process.env.CORS_ORIGIN], + }, + }; const app = await NestFactory.create(ApplicationModule, appOptions); app.use(json({ limit: "50mb" })); app.use(urlencoded({ extended: true, limit: "50mb" })); app.setGlobalPrefix("api"); - await executeProjection(app); + app.connectMicroservice({ + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: ARTICLE_QUEUE, + queueOptions: { + durable: true, + }, + }, + }); + app.connectMicroservice({ + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: USER_QUEUE, + queueOptions: { + durable: true, + }, + }, + }); + app.connectMicroservice({ + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: PROFILE_QUEUE, + queueOptions: { + durable: true, + }, + }, + }); const options = new DocumentBuilder() .setTitle("Social API") @@ -59,6 +81,7 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup("/docs", app, document); + app.startAllMicroservices(); await app.listen(8000); } } diff --git a/src/media/media.module.ts b/src/media/media.module.ts index d872f55..6d70335 100644 --- a/src/media/media.module.ts +++ b/src/media/media.module.ts @@ -5,7 +5,7 @@ import { RequestMethod, } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; -import { AuthMiddleware } from "../user/auth.middleware"; +import { AuthMiddleware } from "../shared/middleware/auth.middleware"; import { UserModule } from "../user/user.module"; import { DropboxService } from "./services/dropbox.service"; import { MediaController } from "./media.controller"; diff --git a/src/profile/commands/command.module.ts b/src/profile/application/commands/command.module.ts similarity index 55% rename from src/profile/commands/command.module.ts rename to src/profile/application/commands/command.module.ts index e90350f..591079b 100644 --- a/src/profile/commands/command.module.ts +++ b/src/profile/application/commands/command.module.ts @@ -1,13 +1,13 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { CommandHandlers } from "."; -import { WRITE_CONNECTION } from "../../config"; -import { RabbitMqModule } from "../../rabbitmq/rabbitmq.module"; -import { UserEntity } from "../../user/core/entities/user.entity"; -import { UserModule } from "../../user/user.module"; -import { FollowsEntity } from "../core/entities/follows.entity"; -import { ProfileController } from "../profile.controller"; +import { WRITE_CONNECTION } from "../../../configs"; +import { RabbitMqModule } from "../../../rabbitmq/rabbitmq.module"; +import { UserEntity } from "../../../user/core/entities/user.entity"; +import { UserModule } from "../../../user/user.module"; +import { FollowsEntity } from "../../core/entities/follows.entity"; import { ProfileService } from "../services/profile.service"; @Module({ @@ -18,7 +18,7 @@ import { ProfileService } from "../services/profile.service"; RabbitMqModule, ], providers: [ProfileService, ...CommandHandlers], - controllers: [ProfileController], + controllers: [], exports: [], }) export class CommandModule {} diff --git a/src/profile/commands/handlers/follow-profile.handler.ts b/src/profile/application/commands/handlers/follow-profile.handler.ts similarity index 71% rename from src/profile/commands/handlers/follow-profile.handler.ts rename to src/profile/application/commands/handlers/follow-profile.handler.ts index 464b1a9..30eb337 100644 --- a/src/profile/commands/handlers/follow-profile.handler.ts +++ b/src/profile/application/commands/handlers/follow-profile.handler.ts @@ -1,17 +1,17 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { PROFILE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { FollowsEntity } from "../../core/entities/follows.entity"; -import { MessageType } from "../../core/enums/profile.enum"; +import { PROFILE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities"; +import { FollowsEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; import { + IPayloadProfileFollowed, ProfileData, ProfileRO, -} from "../../core/interfaces/profile.interface"; +} from "../../../core/interfaces"; import { FollowProfileCommand } from "../impl"; @CommandHandler(FollowProfileCommand) @@ -23,9 +23,11 @@ export class FollowProfileCommandHandler private readonly userRepository: Repository, @InjectRepository(FollowsEntity, WRITE_CONNECTION) private readonly followsRepository: Repository, - - private readonly publisher: PublisherService - ) {} + @Inject(PROFILE_RMQ_CLIENT) + private readonly profileRmqClient: ClientProxy + ) { + this.profileRmqClient.connect(); + } async execute({ followerEmail, @@ -64,12 +66,10 @@ export class FollowProfileCommandHandler ); if (follow) { - this.publisher.publish(PROFILE_QUEUE, { - type: MessageType.PROFILE_FOLLOWED, - payload: { - follow, - }, - }); + this.profileRmqClient.emit( + { cmd: MessageCmd.PROFILE_FOLLOWED }, + { follow } + ); } } diff --git a/src/profile/commands/handlers/index.ts b/src/profile/application/commands/handlers/index.ts similarity index 100% rename from src/profile/commands/handlers/index.ts rename to src/profile/application/commands/handlers/index.ts diff --git a/src/profile/commands/handlers/unfollow-profile.handler.ts b/src/profile/application/commands/handlers/unfollow-profile.handler.ts similarity index 67% rename from src/profile/commands/handlers/unfollow-profile.handler.ts rename to src/profile/application/commands/handlers/unfollow-profile.handler.ts index 1e863d8..c3c0108 100644 --- a/src/profile/commands/handlers/unfollow-profile.handler.ts +++ b/src/profile/application/commands/handlers/unfollow-profile.handler.ts @@ -1,17 +1,18 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { PROFILE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { FollowsEntity } from "../../core/entities/follows.entity"; -import { MessageType } from "../../core/enums/profile.enum"; + +import { PROFILE_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities"; +import { FollowsEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; import { + IPayloadProfileUnFollowed, ProfileData, ProfileRO, -} from "../../core/interfaces/profile.interface"; +} from "../../../core/interfaces"; import { UnFollowProfileCommand } from "../impl"; @CommandHandler(UnFollowProfileCommand) @@ -23,8 +24,8 @@ export class UnFollowProfileCommandHandler private readonly userRepository: Repository, @InjectRepository(FollowsEntity, WRITE_CONNECTION) private readonly followsRepository: Repository, - - private readonly publisher: PublisherService + @Inject(PROFILE_RMQ_CLIENT) + private readonly profileRmqClient: ClientProxy ) {} async execute({ @@ -46,19 +47,18 @@ export class UnFollowProfileCommandHandler HttpStatus.BAD_REQUEST ); } - const follow = { + const follow = new FollowsEntity({ followerId, followingId: followingUser.id, - }; + }); + const _deleted = await this.followsRepository.delete(follow); if (_deleted) { - this.publisher.publish(PROFILE_QUEUE, { - type: MessageType.PROFILE_FOLLOWED, - payload: { - follow, - }, - }); + this.profileRmqClient.emit( + { cmd: MessageCmd.PROFILE_UNFOLLOWED }, + { follow } + ); } let profile: ProfileData = { diff --git a/src/profile/commands/impl/follow-profile.command.ts b/src/profile/application/commands/impl/follow-profile.command.ts similarity index 100% rename from src/profile/commands/impl/follow-profile.command.ts rename to src/profile/application/commands/impl/follow-profile.command.ts diff --git a/src/profile/commands/impl/index.ts b/src/profile/application/commands/impl/index.ts similarity index 100% rename from src/profile/commands/impl/index.ts rename to src/profile/application/commands/impl/index.ts diff --git a/src/profile/commands/impl/unfollow-profile.command.ts b/src/profile/application/commands/impl/unfollow-profile.command.ts similarity index 100% rename from src/profile/commands/impl/unfollow-profile.command.ts rename to src/profile/application/commands/impl/unfollow-profile.command.ts diff --git a/src/profile/commands/index.ts b/src/profile/application/commands/index.ts similarity index 100% rename from src/profile/commands/index.ts rename to src/profile/application/commands/index.ts diff --git a/src/profile/events/event.module.ts b/src/profile/application/events/event.module.ts similarity index 67% rename from src/profile/events/event.module.ts rename to src/profile/application/events/event.module.ts index 4e922a3..49a7161 100644 --- a/src/profile/events/event.module.ts +++ b/src/profile/application/events/event.module.ts @@ -1,9 +1,9 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { READ_CONNECTION } from "../../config"; -import { EventHandlers } from "../events"; -import { FollowsEntity } from "../core/entities/follows.entity"; +import { READ_CONNECTION } from "../../../configs"; +import { EventHandlers } from "."; +import { FollowsEntity } from "../../core/entities/follows.entity"; @Module({ imports: [ diff --git a/src/profile/events/handlers/index.ts b/src/profile/application/events/handlers/index.ts similarity index 100% rename from src/profile/events/handlers/index.ts rename to src/profile/application/events/handlers/index.ts diff --git a/src/profile/events/handlers/profile-followed.handler.ts b/src/profile/application/events/handlers/profile-followed.handler.ts similarity index 86% rename from src/profile/events/handlers/profile-followed.handler.ts rename to src/profile/application/events/handlers/profile-followed.handler.ts index 5048c13..58a2724 100644 --- a/src/profile/events/handlers/profile-followed.handler.ts +++ b/src/profile/application/events/handlers/profile-followed.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { FollowsEntity } from "../../core/entities/follows.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { FollowsEntity } from "../../../core/entities/follows.entity"; import { ProfileFollowedEvent } from "../impl"; @EventsHandler(ProfileFollowedEvent) diff --git a/src/profile/events/handlers/profile-unfollowed.handler.ts b/src/profile/application/events/handlers/profile-unfollowed.handler.ts similarity index 86% rename from src/profile/events/handlers/profile-unfollowed.handler.ts rename to src/profile/application/events/handlers/profile-unfollowed.handler.ts index 8fe8d13..a53426f 100644 --- a/src/profile/events/handlers/profile-unfollowed.handler.ts +++ b/src/profile/application/events/handlers/profile-unfollowed.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { FollowsEntity } from "../../core/entities/follows.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { FollowsEntity } from "../../../core/entities/follows.entity"; import { ProfileUnFollowedEvent } from "../impl"; @EventsHandler(ProfileUnFollowedEvent) diff --git a/src/profile/events/impl/index.ts b/src/profile/application/events/impl/index.ts similarity index 100% rename from src/profile/events/impl/index.ts rename to src/profile/application/events/impl/index.ts diff --git a/src/profile/events/impl/profile-followed.event.ts b/src/profile/application/events/impl/profile-followed.event.ts similarity index 57% rename from src/profile/events/impl/profile-followed.event.ts rename to src/profile/application/events/impl/profile-followed.event.ts index 0ce6de1..81ebb8f 100644 --- a/src/profile/events/impl/profile-followed.event.ts +++ b/src/profile/application/events/impl/profile-followed.event.ts @@ -1,4 +1,4 @@ -import { FollowsEntity } from "../../core/entities/follows.entity"; +import { FollowsEntity } from "../../../core/entities/follows.entity"; export class ProfileFollowedEvent { constructor(public readonly follow: FollowsEntity) {} diff --git a/src/profile/events/impl/profile-unfollowed.event.ts b/src/profile/application/events/impl/profile-unfollowed.event.ts similarity index 56% rename from src/profile/events/impl/profile-unfollowed.event.ts rename to src/profile/application/events/impl/profile-unfollowed.event.ts index e84a5c9..5ba85cf 100644 --- a/src/profile/events/impl/profile-unfollowed.event.ts +++ b/src/profile/application/events/impl/profile-unfollowed.event.ts @@ -1,4 +1,4 @@ -import { IFollow } from "../../core/interfaces/profile.interface"; +import { IFollow } from "../../../core/interfaces/profile.interface"; export class ProfileUnFollowedEvent { constructor(public readonly follow: IFollow) {} diff --git a/src/profile/events/index.ts b/src/profile/application/events/index.ts similarity index 100% rename from src/profile/events/index.ts rename to src/profile/application/events/index.ts diff --git a/src/profile/queries/handlers/find-profile.handler.ts b/src/profile/application/queries/handlers/find-profile.handler.ts similarity index 82% rename from src/profile/queries/handlers/find-profile.handler.ts rename to src/profile/application/queries/handlers/find-profile.handler.ts index 8b5b589..9256390 100644 --- a/src/profile/queries/handlers/find-profile.handler.ts +++ b/src/profile/application/queries/handlers/find-profile.handler.ts @@ -1,13 +1,13 @@ import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { UserEntity } from "../../../user/core/entities/user.entity"; -import { FollowsEntity } from "../../core/entities/follows.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../../user/core/entities/user.entity"; +import { FollowsEntity } from "../../../core/entities/follows.entity"; import { ProfileData, ProfileRO, -} from "../../core/interfaces/profile.interface"; +} from "../../../core/interfaces/profile.interface"; import { FindProfileQuery } from "../impl"; @QueryHandler(FindProfileQuery) diff --git a/src/profile/queries/handlers/index.ts b/src/profile/application/queries/handlers/index.ts similarity index 100% rename from src/profile/queries/handlers/index.ts rename to src/profile/application/queries/handlers/index.ts diff --git a/src/profile/queries/impl/find-profile.query.ts b/src/profile/application/queries/impl/find-profile.query.ts similarity index 100% rename from src/profile/queries/impl/find-profile.query.ts rename to src/profile/application/queries/impl/find-profile.query.ts diff --git a/src/profile/queries/impl/index.ts b/src/profile/application/queries/impl/index.ts similarity index 100% rename from src/profile/queries/impl/index.ts rename to src/profile/application/queries/impl/index.ts diff --git a/src/profile/queries/index.ts b/src/profile/application/queries/index.ts similarity index 100% rename from src/profile/queries/index.ts rename to src/profile/application/queries/index.ts diff --git a/src/profile/queries/query.module.ts b/src/profile/application/queries/query.module.ts similarity index 58% rename from src/profile/queries/query.module.ts rename to src/profile/application/queries/query.module.ts index cbe5f28..b40f8dc 100644 --- a/src/profile/queries/query.module.ts +++ b/src/profile/application/queries/query.module.ts @@ -1,12 +1,12 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { QueryHandlers } from "."; -import { READ_CONNECTION } from "../../config"; -import { UserEntity } from "../../user/core/entities/user.entity"; -import { UserModule } from "../../user/user.module"; -import { FollowsEntity } from "../core/entities/follows.entity"; -import { ProfileController } from "../profile.controller"; +import { READ_CONNECTION } from "../../../configs"; +import { UserEntity } from "../../../user/core/entities/user.entity"; +import { UserModule } from "../../../user/user.module"; +import { FollowsEntity } from "../../core/entities/follows.entity"; import { ProfileService } from "../services/profile.service"; @Module({ @@ -16,7 +16,7 @@ import { ProfileService } from "../services/profile.service"; CqrsModule, ], providers: [ProfileService, ...QueryHandlers], - controllers: [ProfileController], + controllers: [], exports: [], }) export class QueryModule {} diff --git a/src/profile/application/services/index.ts b/src/profile/application/services/index.ts new file mode 100644 index 0000000..329fffa --- /dev/null +++ b/src/profile/application/services/index.ts @@ -0,0 +1 @@ +export * from "./profile.service"; diff --git a/src/profile/services/profile.service.ts b/src/profile/application/services/profile.service.ts similarity index 100% rename from src/profile/services/profile.service.ts rename to src/profile/application/services/profile.service.ts diff --git a/src/profile/core/enums/profile.enum.ts b/src/profile/core/enums/profile.enum.ts index f922188..e5ba1e4 100644 --- a/src/profile/core/enums/profile.enum.ts +++ b/src/profile/core/enums/profile.enum.ts @@ -1,4 +1,4 @@ -export enum MessageType { +export enum MessageCmd { PROFILE_FOLLOWED = "PROFILE_FOLLOWED", PROFILE_UNFOLLOWED = "PROFILE_UNFOLLOWED", } diff --git a/src/profile/core/interfaces/index.ts b/src/profile/core/interfaces/index.ts index e76d83f..fb6322e 100644 --- a/src/profile/core/interfaces/index.ts +++ b/src/profile/core/interfaces/index.ts @@ -1,2 +1 @@ export * from "./profile.interface"; -export * from "./projection.interface"; diff --git a/src/profile/core/interfaces/profile.interface.ts b/src/profile/core/interfaces/profile.interface.ts index fa206ad..7f703ee 100644 --- a/src/profile/core/interfaces/profile.interface.ts +++ b/src/profile/core/interfaces/profile.interface.ts @@ -1,3 +1,5 @@ +import { FollowsEntity } from "../entities"; + export interface ProfileData { username: string; bio: string; @@ -14,3 +16,9 @@ export interface IFollow { followerId?: number; followingId?: number; } + +export interface IPayloadProfileRmq { + follow: FollowsEntity; +} +export interface IPayloadProfileFollowed extends IPayloadProfileRmq {} +export interface IPayloadProfileUnFollowed extends IPayloadProfileRmq {} diff --git a/src/profile/core/interfaces/projection.interface.ts b/src/profile/core/interfaces/projection.interface.ts deleted file mode 100644 index 6d31245..0000000 --- a/src/profile/core/interfaces/projection.interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { FollowsEntity } from "../entities/follows.entity"; -import { MessageType } from "../enums"; -import { IFollow } from "./profile.interface"; - -export interface IProjection { - handle(): Promise; -} - -export interface IMessage { - type: MessageType; - payload: { - follow: FollowsEntity | IFollow; - }; -} diff --git a/src/profile/presentation/rest/index.ts b/src/profile/presentation/rest/index.ts new file mode 100644 index 0000000..a9e4573 --- /dev/null +++ b/src/profile/presentation/rest/index.ts @@ -0,0 +1 @@ +export * from "./profile.controller"; diff --git a/src/profile/profile.controller.ts b/src/profile/presentation/rest/profile.controller.ts similarity index 80% rename from src/profile/profile.controller.ts rename to src/profile/presentation/rest/profile.controller.ts index 2680946..1407d59 100644 --- a/src/profile/profile.controller.ts +++ b/src/profile/presentation/rest/profile.controller.ts @@ -1,10 +1,14 @@ import { Controller, Delete, Get, Param, Post } from "@nestjs/common"; import { CommandBus, QueryBus } from "@nestjs/cqrs"; import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; -import { User } from "../user/user.decorator"; -import { FollowProfileCommand, UnFollowProfileCommand } from "./commands"; -import { FindProfileQuery } from "./queries"; -import { ProfileRO } from "./core/interfaces/profile.interface"; + +import { User } from "../../../shared/middleware/user.decorator"; +import { + FollowProfileCommand, + UnFollowProfileCommand, +} from "../../application/commands"; +import { FindProfileQuery } from "../../application/queries"; +import { ProfileRO } from "../../core/interfaces/profile.interface"; @ApiBearerAuth() @ApiTags("profiles") diff --git a/src/profile/presentation/rmq/index.ts b/src/profile/presentation/rmq/index.ts new file mode 100644 index 0000000..2376fd2 --- /dev/null +++ b/src/profile/presentation/rmq/index.ts @@ -0,0 +1 @@ +export * from "./profile.rmq"; diff --git a/src/profile/presentation/rmq/profile.rmq.ts b/src/profile/presentation/rmq/profile.rmq.ts new file mode 100644 index 0000000..b71ab6e --- /dev/null +++ b/src/profile/presentation/rmq/profile.rmq.ts @@ -0,0 +1,28 @@ +import { Controller } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; +import { EventPattern, Payload, Transport } from "@nestjs/microservices"; + +import { + ProfileFollowedEvent, + ProfileUnFollowedEvent, +} from "../../application/events"; +import { + IPayloadProfileFollowed, + IPayloadProfileUnFollowed, + MessageCmd, +} from "../../core"; + +@Controller() +export class ProfileRmq { + constructor(private readonly eventBus: EventBus) {} + + @EventPattern({ cmd: MessageCmd.PROFILE_FOLLOWED }, Transport.RMQ) + async handleProfileFollowed(@Payload() payload: IPayloadProfileFollowed) { + return this.eventBus.publish(new ProfileFollowedEvent(payload.follow)); + } + + @EventPattern({ cmd: MessageCmd.PROFILE_UNFOLLOWED }, Transport.RMQ) + async handleProfileUnFollowed(@Payload() payload: IPayloadProfileUnFollowed) { + return this.eventBus.publish(new ProfileUnFollowedEvent(payload.follow)); + } +} diff --git a/src/profile/profile.module.ts b/src/profile/profile.module.ts index b9ac4bc..eaa7efc 100644 --- a/src/profile/profile.module.ts +++ b/src/profile/profile.module.ts @@ -6,15 +6,15 @@ import { } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { RabbitMqModule } from "../rabbitmq/rabbitmq.module"; -import { AuthMiddleware } from "../user/auth.middleware"; -import { UserModule } from "../user/user.module"; -import { CommandModule } from "./commands/command.module"; -import { EventModule } from "./events/event.module"; -import { ProfileController } from "./profile.controller"; -import { ProfileProjection } from "./profile.projection"; -import { ProfileService } from "./services/profile.service"; -import { QueryModule } from "./queries/query.module"; import { RedisModule } from "../redis/redis.module"; +import { AuthMiddleware } from "../shared/middleware/auth.middleware"; +import { UserModule } from "../user/user.module"; +import { CommandModule } from "./application/commands/command.module"; +import { EventModule } from "./application/events/event.module"; +import { QueryModule } from "./application/queries/query.module"; +import { ProfileService } from "./application/services"; +import { ProfileController } from "./presentation/rest"; +import { ProfileRmq } from "./presentation/rmq"; @Module({ imports: [ @@ -26,8 +26,8 @@ import { RedisModule } from "../redis/redis.module"; RabbitMqModule, RedisModule, ], - providers: [ProfileService, ProfileProjection], - controllers: [ProfileController], + providers: [ProfileService], + controllers: [ProfileController, ProfileRmq], exports: [], }) export class ProfileModule implements NestModule { diff --git a/src/profile/profile.projection.ts b/src/profile/profile.projection.ts deleted file mode 100644 index f3c714e..0000000 --- a/src/profile/profile.projection.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { EventBus } from "@nestjs/cqrs"; -import { ConsumerService } from "../rabbitmq/consumer.service"; -import { PROFILE_QUEUE } from "../rabbitmq/rabbitmq.constants"; -import { IMessage, IProjection } from "./core"; -import { FollowsEntity } from "./core/entities/follows.entity"; -import { MessageType } from "./core/enums/profile.enum"; -import { ProfileFollowedEvent, ProfileUnFollowedEvent } from "./events"; - -@Injectable() -export class ProfileProjection implements IProjection { - constructor( - private readonly consumer: ConsumerService, - private readonly eventBus: EventBus - ) {} - - async handle() { - await this.consumer.consume(PROFILE_QUEUE, (msg: IMessage) => { - this.handleMessage(msg); - }); - } - - private async handleMessage({ type, payload }: IMessage) { - switch (type) { - case MessageType.PROFILE_FOLLOWED: - this.eventBus.publish( - new ProfileFollowedEvent(payload.follow as FollowsEntity) - ); - break; - case MessageType.PROFILE_UNFOLLOWED: - this.eventBus.publish(new ProfileUnFollowedEvent(payload.follow)); - break; - default: - break; - } - } -} diff --git a/src/rabbitmq/consumer.service.ts b/src/rabbitmq/consumer.service.ts deleted file mode 100644 index aaa20c5..0000000 --- a/src/rabbitmq/consumer.service.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Inject, Injectable, Logger } from "@nestjs/common"; -import { Connection, Channel } from "amqplib"; -import { - ARTICLE_DLQ, - ARTICLE_DL_ROUTE_KEY, - ARTICLE_QUEUE, - PROFILE_DLQ, - PROFILE_DL_ROUTE_KEY, - PROFILE_QUEUE, - RABBIT_DL_EXCHANGE, - RABBIT_EXCHANGE, - USER_DLQ, - USER_DL_ROUTE_KEY, - USER_QUEUE, -} from "./rabbitmq.constants"; - -@Injectable() -export class ConsumerService { - private channel: Channel; - private dlChannel: Channel; - - constructor( - @Inject("RABBIT_MQ_CONNECTION") - private connection: Connection - ) { - this.init(); - } - - async init() { - this.channel = await this.connection.createChannel(); - this.dlChannel = await this.connection.createChannel(); - } - - async consume(queueName: string, callback: (msg: any) => void) { - if (this.channel) { - await this.channel.assertExchange(RABBIT_EXCHANGE, "direct", { - durable: true, - }); - await this.channel.assertQueue(queueName, { durable: true }); - this.channel.consume(queueName, async (msg) => { - if (msg !== null) { - try { - callback(JSON.parse(msg.content.toString())); - this.channel.ack(msg); - } catch (error) { - Logger.error(`Error processing message: ${error.message}`); - // Move message to its respective DLQ based on the queueName - switch (queueName) { - case ARTICLE_QUEUE: - await this.publishToDLQ(ARTICLE_DLQ, msg.content.toString()); - break; - case USER_QUEUE: - await this.publishToDLQ(USER_DLQ, msg.content.toString()); - break; - case PROFILE_QUEUE: - await this.publishToDLQ(PROFILE_DLQ, msg.content.toString()); - break; - default: - Logger.warn(`Unknown queue name: ${queueName}`); - break; - } - // Reject the message so that it is removed from the queue - this.channel.nack(msg, false, false); - } - } - }); - - Logger.log(`Started consuming messages from queue '${queueName}'`); - } - } - - private async publishToDLQ(queueName: string, message: any) { - await this.dlChannel.assertExchange(RABBIT_DL_EXCHANGE, "direct", { - durable: true, - }); - await this.channel.assertQueue(queueName, { durable: true }); - - switch (queueName) { - case ARTICLE_DLQ: - await this.channel.bindQueue( - ARTICLE_DLQ, - RABBIT_DL_EXCHANGE, - ARTICLE_DL_ROUTE_KEY - ); - this.channel.publish( - RABBIT_DL_EXCHANGE, - ARTICLE_DL_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message} sent to exchange ${RABBIT_DL_EXCHANGE} with route key ${ARTICLE_DL_ROUTE_KEY}` - ); - break; - case USER_DLQ: - await this.channel.bindQueue( - USER_DLQ, - RABBIT_DL_EXCHANGE, - USER_DL_ROUTE_KEY - ); - this.channel.publish( - RABBIT_DL_EXCHANGE, - USER_DL_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message} sent to exchange ${RABBIT_DL_EXCHANGE} with route key ${USER_DL_ROUTE_KEY}` - ); - break; - case PROFILE_DLQ: - await this.channel.bindQueue( - PROFILE_DLQ, - RABBIT_DL_EXCHANGE, - PROFILE_DL_ROUTE_KEY - ); - this.channel.publish( - RABBIT_DL_EXCHANGE, - PROFILE_DL_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message} sent to exchange ${RABBIT_DL_EXCHANGE} with route key ${PROFILE_DL_ROUTE_KEY}` - ); - break; - default: - Logger.warn(`Unknown queue name: ${queueName}`); - break; - } - } -} diff --git a/src/rabbitmq/publisher.service.ts b/src/rabbitmq/publisher.service.ts deleted file mode 100644 index 08fb2ac..0000000 --- a/src/rabbitmq/publisher.service.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Inject, Injectable, Logger } from "@nestjs/common"; -import { Connection, Channel } from "amqplib"; -import { - ARTICLE_DLQ, - ARTICLE_DL_ROUTE_KEY, - ARTICLE_QUEUE, - ARTICLE_ROUTE_KEY, - PROFILE_DL_ROUTE_KEY, - PROFILE_QUEUE, - PROFILE_ROUTE_KEY, - RABBIT_DL_EXCHANGE, - RABBIT_EXCHANGE, - USER_DL_ROUTE_KEY, - USER_QUEUE, - USER_ROUTE_KEY, -} from "./rabbitmq.constants"; - -@Injectable() -export class PublisherService { - private channel: Channel; - - constructor( - @Inject("RABBIT_MQ_CONNECTION") - private connection: Connection - ) { - this.connection - .createChannel() - .then((channelCreated) => (this.channel = channelCreated)); - } - - async publish(queueName: string, message: any) { - await this.channel.assertExchange(RABBIT_EXCHANGE, "direct", { - durable: true, - }); - - await this.channel.assertQueue(queueName, { - durable: true, - }); - - switch (queueName) { - case ARTICLE_QUEUE: - await this.channel.bindQueue( - ARTICLE_QUEUE, - RABBIT_EXCHANGE, - ARTICLE_ROUTE_KEY - ); - this.channel.publish( - RABBIT_EXCHANGE, - ARTICLE_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message.type} sent to exchange ${RABBIT_EXCHANGE} with route key ${ARTICLE_ROUTE_KEY}` - ); - break; - case USER_QUEUE: - await this.channel.bindQueue( - USER_QUEUE, - RABBIT_EXCHANGE, - USER_ROUTE_KEY - ); - this.channel.publish( - RABBIT_EXCHANGE, - USER_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message.type} sent to exchange ${RABBIT_EXCHANGE} with route key ${USER_ROUTE_KEY}` - ); - break; - case PROFILE_QUEUE: - await this.channel.bindQueue( - PROFILE_QUEUE, - RABBIT_EXCHANGE, - PROFILE_ROUTE_KEY - ); - this.channel.publish( - RABBIT_EXCHANGE, - PROFILE_ROUTE_KEY, - Buffer.from(JSON.stringify(message)), - { - persistent: true, - } - ); - - Logger.log( - `Message type: ${message.type} sent to exchange ${RABBIT_EXCHANGE} with route key ${PROFILE_ROUTE_KEY}` - ); - break; - default: - break; - } - } -} diff --git a/src/rabbitmq/rabbitmq.module.ts b/src/rabbitmq/rabbitmq.module.ts index fd028af..0859f41 100644 --- a/src/rabbitmq/rabbitmq.module.ts +++ b/src/rabbitmq/rabbitmq.module.ts @@ -1,27 +1,53 @@ import { Module } from "@nestjs/common"; -import { connect, Connection } from "amqplib"; -import { ConsumerService } from "./consumer.service"; -import { PublisherService } from "./publisher.service"; +import { ClientsModule, Transport } from "@nestjs/microservices"; + +import { + ARTICLE_QUEUE, + ARTICLE_RMQ_CLIENT, + PROFILE_QUEUE, + PROFILE_RMQ_CLIENT, + USER_QUEUE, + USER_RMQ_CLIENT, +} from "../configs"; @Module({ - imports: [], - providers: [ - { - provide: "RABBIT_MQ_CONNECTION", - useFactory: async (): Promise => { - if (process.env.NODE_ENV === "development") { - return ( - connect("amqp://localhost") || connect(process.env.RABBIT_URL_DEV) - ); - } - if (process.env.NODE_ENV === "staging") { - return connect(process.env.RABBIT_URL); - } + imports: [ + ClientsModule.register([ + { + name: ARTICLE_RMQ_CLIENT, + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: ARTICLE_QUEUE, + queueOptions: { + durable: true, + }, + }, + }, + { + name: USER_RMQ_CLIENT, + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: USER_QUEUE, + queueOptions: { + durable: true, + }, + }, + }, + { + name: PROFILE_RMQ_CLIENT, + transport: Transport.RMQ, + options: { + urls: [process.env.RABBIT_URL], + queue: PROFILE_QUEUE, + queueOptions: { + durable: true, + }, + }, }, - }, - PublisherService, - ConsumerService, + ]), ], - exports: ["RABBIT_MQ_CONNECTION", PublisherService, ConsumerService], + exports: [ClientsModule], }) export class RabbitMqModule {} diff --git a/src/redis/redis.module.ts b/src/redis/redis.module.ts index f2430dc..44d5c13 100644 --- a/src/redis/redis.module.ts +++ b/src/redis/redis.module.ts @@ -7,21 +7,13 @@ import { RedisService } from "./redis.service"; { provide: "REDIS_CLIENT", useFactory: () => { - if (process.env.NODE_ENV === "development") { - return new Redis({ - host: "localhost", - port: 6379, - }); - } - if (process.env.NODE_ENV === "staging") { + if (process.env.NODE_ENV !== "development") { return new Redis({ host: process.env.REDIS_HOST, - port: parseInt(process.env.REDIS_PORT), + port: Number(process.env.REDIS_PORT), }); } - if (process.env.NODE_ENV === "RENDER") { - return new Redis(process.env.REDIS_URL); - } + return new Redis(process.env.REDIS_URL); }, }, RedisService, diff --git a/src/shared/base.controller.ts b/src/shared/base.controller.ts index ca48827..c8db17d 100644 --- a/src/shared/base.controller.ts +++ b/src/shared/base.controller.ts @@ -1,15 +1,14 @@ -import { SECRET } from '../config'; -import * as jwt from 'jsonwebtoken'; +import * as jwt from "jsonwebtoken"; +import { JWT_SECRET_KEY } from "../configs"; export class BaseController { - constructor() {} protected getUserIdFromToken(authorization) { if (!authorization) return null; - const token = authorization.split(' ')[1]; - const decoded: any = jwt.verify(token, SECRET); + const token = authorization.split(" ")[1]; + const decoded: any = jwt.verify(token, JWT_SECRET_KEY); return decoded.id; } -} \ No newline at end of file +} diff --git a/src/user/auth.middleware.ts b/src/shared/middleware/auth.middleware.ts similarity index 81% rename from src/user/auth.middleware.ts rename to src/shared/middleware/auth.middleware.ts index 58d93f2..3c7a125 100644 --- a/src/user/auth.middleware.ts +++ b/src/shared/middleware/auth.middleware.ts @@ -4,10 +4,11 @@ import { QueryBus } from "@nestjs/cqrs"; import { NextFunction, Request, Response } from "express"; import { IncomingHttpHeaders } from "http"; import * as jwt from "jsonwebtoken"; -import { SECRET } from "../config"; -import { FindUserById } from "./queries"; -import { UserData } from "./core/interfaces/user.interface"; -import { RedisService } from "../redis/redis.service"; + +import { JWT_SECRET_KEY } from "../../configs"; +import { RedisService } from "../../redis/redis.service"; +import { FindUserById } from "../../user/application/queries"; +import { UserData } from "../../user/core/interfaces/user.interface"; interface IRequestCustom extends Request { user: UserData; @@ -25,7 +26,7 @@ export class AuthMiddleware implements NestMiddleware { const authHeaders = req.headers.authorization; if (authHeaders && (authHeaders as string).split(" ")[1]) { const token = (authHeaders as string).split(" ")[1]; - const decoded: any = jwt.verify(token, SECRET); + const decoded: any = jwt.verify(token, JWT_SECRET_KEY); const _user = await this.redisCacheService.get(decoded.id); if (_user) { diff --git a/src/user/user.decorator.ts b/src/shared/middleware/user.decorator.ts similarity index 77% rename from src/user/user.decorator.ts rename to src/shared/middleware/user.decorator.ts index d2fbfda..b27c03b 100644 --- a/src/user/user.decorator.ts +++ b/src/shared/middleware/user.decorator.ts @@ -1,7 +1,8 @@ import { createParamDecorator, ExecutionContext } from "@nestjs/common"; -import { SECRET } from "../config"; import * as jwt from "jsonwebtoken"; -import { CurrentUser } from "./core/interfaces/user.interface"; + +import { JWT_SECRET_KEY } from "../../configs"; +import { CurrentUser } from "../../user/core/interfaces/user.interface"; export const User = createParamDecorator((data: any, ctx: ExecutionContext) => { const req = ctx.switchToHttp().getRequest(); @@ -15,7 +16,7 @@ export const User = createParamDecorator((data: any, ctx: ExecutionContext) => { ? (req.headers.authorization as string).split(" ") : null; if (token && token[1]) { - const decoded: CurrentUser = jwt.verify(token[1], SECRET); + const decoded: CurrentUser = jwt.verify(token[1], JWT_SECRET_KEY); return !!data ? decoded[data] : decoded; } }); diff --git a/src/tag/tag.module.ts b/src/tag/tag.module.ts index cc0389d..9a0e810 100644 --- a/src/tag/tag.module.ts +++ b/src/tag/tag.module.ts @@ -1,6 +1,6 @@ import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { READ_CONNECTION } from "../config"; +import { READ_CONNECTION } from "../configs"; import { UserModule } from "../user/user.module"; import { TagController } from "./tag.controller"; import { TagEntity } from "./core/entities/tag.entity"; diff --git a/src/tag/tag.service.ts b/src/tag/tag.service.ts index 9d68902..b4d4f7f 100644 --- a/src/tag/tag.service.ts +++ b/src/tag/tag.service.ts @@ -1,7 +1,7 @@ import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../config"; +import { READ_CONNECTION } from "../configs"; import { TagEntity } from "./core/entities/tag.entity"; import { TagRO } from "./core/interfaces/tag.interface"; diff --git a/src/user/commands/command.module.ts b/src/user/application/commands/command.module.ts similarity index 65% rename from src/user/commands/command.module.ts rename to src/user/application/commands/command.module.ts index 26d0662..9397f6b 100644 --- a/src/user/commands/command.module.ts +++ b/src/user/application/commands/command.module.ts @@ -1,11 +1,11 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { CommandHandlers } from "."; -import { WRITE_CONNECTION } from "../../config"; -import { RabbitMqModule } from "../../rabbitmq/rabbitmq.module"; -import { UserController } from "../user.controller"; -import { UserEntity } from "../core"; +import { WRITE_CONNECTION } from "../../../configs"; +import { RabbitMqModule } from "../../../rabbitmq/rabbitmq.module"; +import { UserEntity } from "../../core"; import { UserService } from "../services/user.service"; @Module({ @@ -15,7 +15,7 @@ import { UserService } from "../services/user.service"; RabbitMqModule, ], providers: [UserService, ...CommandHandlers], - controllers: [UserController], + controllers: [], exports: [], }) export class CommandModule {} diff --git a/src/user/commands/handlers/create-user.handler.ts b/src/user/application/commands/handlers/create-user.handler.ts similarity index 68% rename from src/user/commands/handlers/create-user.handler.ts rename to src/user/application/commands/handlers/create-user.handler.ts index 6381ff3..57eedf0 100644 --- a/src/user/commands/handlers/create-user.handler.ts +++ b/src/user/application/commands/handlers/create-user.handler.ts @@ -1,15 +1,15 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { validate } from "class-validator"; -import { getRepository, Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { USER_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../core/entities/user.entity"; -import { MessageType } from "../../core/enums/user.enum"; -import { UserRO } from "../../core/interfaces/user.interface"; -import { UserService } from "../../services/user.service"; +import { Repository, getRepository } from "typeorm"; + +import { USER_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { IPayloadUserCreated, UserRO } from "../../../core/interfaces"; +import { UserService } from "../../services"; import { CreateUserCommand } from "../impl"; @CommandHandler(CreateUserCommand) @@ -19,10 +19,13 @@ export class CreateUserCommandHandler constructor( @InjectRepository(UserEntity, WRITE_CONNECTION) private readonly userRepository: Repository, + @Inject(USER_RMQ_CLIENT) + private readonly userRmqClient: ClientProxy, - private readonly userService: UserService, - private readonly publisher: PublisherService - ) {} + private readonly userService: UserService + ) { + this.userRmqClient.connect(); + } async execute({ dto }: CreateUserCommand): Promise { try { @@ -62,12 +65,10 @@ export class CreateUserCommandHandler const savedUser = await this.userRepository.save(newUser); if (savedUser) { - this.publisher.publish(USER_QUEUE, { - type: MessageType.USER_CREATED, - payload: { - user: savedUser, - }, - }); + this.userRmqClient.emit( + { cmd: MessageCmd.USER_CREATED }, + { user: savedUser } + ); } return this.userService.buildUserRO(savedUser); diff --git a/src/user/commands/handlers/index.ts b/src/user/application/commands/handlers/index.ts similarity index 100% rename from src/user/commands/handlers/index.ts rename to src/user/application/commands/handlers/index.ts diff --git a/src/user/commands/handlers/update-user.handler.ts b/src/user/application/commands/handlers/update-user.handler.ts similarity index 55% rename from src/user/commands/handlers/update-user.handler.ts rename to src/user/application/commands/handlers/update-user.handler.ts index a8522eb..43619d4 100644 --- a/src/user/commands/handlers/update-user.handler.ts +++ b/src/user/application/commands/handlers/update-user.handler.ts @@ -1,14 +1,14 @@ -import { HttpException, HttpStatus } from "@nestjs/common"; +import { HttpException, HttpStatus, Inject } from "@nestjs/common"; import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; +import { ClientProxy } from "@nestjs/microservices"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { WRITE_CONNECTION } from "../../../config"; -import { PublisherService } from "../../../rabbitmq/publisher.service"; -import { USER_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; -import { UserEntity } from "../../core/entities/user.entity"; -import { MessageType } from "../../core/enums/user.enum"; -import { UserRO } from "../../core/interfaces/user.interface"; -import { UserService } from "../../services/user.service"; + +import { USER_RMQ_CLIENT, WRITE_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../core/entities"; +import { MessageCmd } from "../../../core/enums"; +import { IPayloadUserUpdated, UserRO } from "../../../core/interfaces"; +import { UserService } from "../../services"; import { UpdateUserCommand } from "../impl"; @CommandHandler(UpdateUserCommand) @@ -18,10 +18,13 @@ export class UpdateUserCommandHandler constructor( @InjectRepository(UserEntity, WRITE_CONNECTION) private readonly userRepository: Repository, + @Inject(USER_RMQ_CLIENT) + private readonly userRmqClient: ClientProxy, - private readonly userService: UserService, - private readonly publisher: PublisherService - ) {} + private readonly userService: UserService + ) { + this.userRmqClient.connect(); + } async execute({ id, dto }: UpdateUserCommand): Promise { try { @@ -33,12 +36,10 @@ export class UpdateUserCommandHandler const userUpdated = await this.userRepository.save(updated); if (userUpdated) { - this.publisher.publish(USER_QUEUE, { - type: MessageType.USER_UPDATED, - payload: { - user: userUpdated, - }, - }); + this.userRmqClient.emit( + { cmd: MessageCmd.USER_UPDATED }, + { user: userUpdated } + ); } return this.userService.buildUserRO(userUpdated); diff --git a/src/user/commands/impl/create-user.command.ts b/src/user/application/commands/impl/create-user.command.ts similarity index 63% rename from src/user/commands/impl/create-user.command.ts rename to src/user/application/commands/impl/create-user.command.ts index 8edfb46..3d81841 100644 --- a/src/user/commands/impl/create-user.command.ts +++ b/src/user/application/commands/impl/create-user.command.ts @@ -1,4 +1,4 @@ -import { CreateUserDto } from "../../dto"; +import { CreateUserDto } from "../../../core/dto"; export class CreateUserCommand { constructor(public readonly dto: CreateUserDto) {} diff --git a/src/user/commands/impl/index.ts b/src/user/application/commands/impl/index.ts similarity index 100% rename from src/user/commands/impl/index.ts rename to src/user/application/commands/impl/index.ts diff --git a/src/user/commands/impl/update-user.command.ts b/src/user/application/commands/impl/update-user.command.ts similarity index 69% rename from src/user/commands/impl/update-user.command.ts rename to src/user/application/commands/impl/update-user.command.ts index 0eca571..55b69b8 100644 --- a/src/user/commands/impl/update-user.command.ts +++ b/src/user/application/commands/impl/update-user.command.ts @@ -1,4 +1,4 @@ -import { UpdateUserDto } from "../../dto"; +import { UpdateUserDto } from "../../../core/dto"; export class UpdateUserCommand { constructor(public readonly id: number, public readonly dto: UpdateUserDto) {} diff --git a/src/user/commands/index.ts b/src/user/application/commands/index.ts similarity index 100% rename from src/user/commands/index.ts rename to src/user/application/commands/index.ts diff --git a/src/user/events/event.module.ts b/src/user/application/events/event.module.ts similarity index 71% rename from src/user/events/event.module.ts rename to src/user/application/events/event.module.ts index 7f46ff8..13161e9 100644 --- a/src/user/events/event.module.ts +++ b/src/user/application/events/event.module.ts @@ -1,9 +1,9 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; -import { READ_CONNECTION } from "../../config"; -import { UserEntity } from "../core"; -import { EventHandlers } from "../events"; +import { READ_CONNECTION } from "../../../configs"; +import { UserEntity } from "../../core"; +import { EventHandlers } from "."; @Module({ imports: [ diff --git a/src/user/events/handlers/index.ts b/src/user/application/events/handlers/index.ts similarity index 100% rename from src/user/events/handlers/index.ts rename to src/user/application/events/handlers/index.ts diff --git a/src/user/events/handlers/user-created.handler.ts b/src/user/application/events/handlers/user-created.handler.ts similarity index 86% rename from src/user/events/handlers/user-created.handler.ts rename to src/user/application/events/handlers/user-created.handler.ts index d118158..f7e39a9 100644 --- a/src/user/events/handlers/user-created.handler.ts +++ b/src/user/application/events/handlers/user-created.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { UserEntity } from "../../core/entities/user.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../core/entities/user.entity"; import { UserCreatedEvent } from "../impl"; @EventsHandler(UserCreatedEvent) diff --git a/src/user/events/handlers/user-updated.handler.ts b/src/user/application/events/handlers/user-updated.handler.ts similarity index 86% rename from src/user/events/handlers/user-updated.handler.ts rename to src/user/application/events/handlers/user-updated.handler.ts index a75b582..d1a8a00 100644 --- a/src/user/events/handlers/user-updated.handler.ts +++ b/src/user/application/events/handlers/user-updated.handler.ts @@ -3,8 +3,8 @@ import { IEventHandler } from "@nestjs/cqrs"; import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { UserEntity } from "../../core/entities/user.entity"; +import { READ_CONNECTION } from "../../../../configs"; +import { UserEntity } from "../../../core/entities/user.entity"; import { UserUpdatedEvent } from "../impl"; @EventsHandler(UserUpdatedEvent) diff --git a/src/user/events/impl/index.ts b/src/user/application/events/impl/index.ts similarity index 100% rename from src/user/events/impl/index.ts rename to src/user/application/events/impl/index.ts diff --git a/src/user/events/impl/user-created.event.ts b/src/user/application/events/impl/user-created.event.ts similarity index 56% rename from src/user/events/impl/user-created.event.ts rename to src/user/application/events/impl/user-created.event.ts index 4d38585..702e7b3 100644 --- a/src/user/events/impl/user-created.event.ts +++ b/src/user/application/events/impl/user-created.event.ts @@ -1,4 +1,4 @@ -import { UserEntity } from "../../core/entities/user.entity"; +import { UserEntity } from "../../../core/entities/user.entity"; export class UserCreatedEvent { constructor(public readonly user: UserEntity) {} diff --git a/src/user/events/impl/user-updated.event.ts b/src/user/application/events/impl/user-updated.event.ts similarity index 56% rename from src/user/events/impl/user-updated.event.ts rename to src/user/application/events/impl/user-updated.event.ts index 9b2a28f..797c103 100644 --- a/src/user/events/impl/user-updated.event.ts +++ b/src/user/application/events/impl/user-updated.event.ts @@ -1,4 +1,4 @@ -import { UserEntity } from "../../core/entities/user.entity"; +import { UserEntity } from "../../../core/entities/user.entity"; export class UserUpdatedEvent { constructor(public readonly user: UserEntity) {} diff --git a/src/user/events/index.ts b/src/user/application/events/index.ts similarity index 100% rename from src/user/events/index.ts rename to src/user/application/events/index.ts diff --git a/src/user/queries/handlers/find-user-by-email.handler.ts b/src/user/application/queries/handlers/find-user-by-email.handler.ts similarity index 76% rename from src/user/queries/handlers/find-user-by-email.handler.ts rename to src/user/application/queries/handlers/find-user-by-email.handler.ts index bf42c2e..d33e4d3 100644 --- a/src/user/queries/handlers/find-user-by-email.handler.ts +++ b/src/user/application/queries/handlers/find-user-by-email.handler.ts @@ -1,10 +1,10 @@ import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { RedisService } from "../../../redis/redis.service"; -import { UserEntity } from "../../core/entities/user.entity"; -import { UserRO } from "../../core/interfaces/user.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { RedisService } from "../../../../redis/redis.service"; +import { UserEntity } from "../../../core/entities/user.entity"; +import { UserRO } from "../../../core/interfaces/user.interface"; import { FindUserByEmailQuery } from "../impl"; @QueryHandler(FindUserByEmailQuery) diff --git a/src/user/queries/handlers/find-user-by-id.handler.ts b/src/user/application/queries/handlers/find-user-by-id.handler.ts similarity index 54% rename from src/user/queries/handlers/find-user-by-id.handler.ts rename to src/user/application/queries/handlers/find-user-by-id.handler.ts index 7ff1f8c..67258ae 100644 --- a/src/user/queries/handlers/find-user-by-id.handler.ts +++ b/src/user/application/queries/handlers/find-user-by-id.handler.ts @@ -1,11 +1,11 @@ -import { HttpException } from "@nestjs/common"; +import { HttpException, HttpStatus } from "@nestjs/common"; import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; -import { READ_CONNECTION } from "../../../config"; -import { RedisService } from "../../../redis/redis.service"; -import { UserEntity } from "../../core/entities/user.entity"; -import { UserRO } from "../../core/interfaces/user.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { RedisService } from "../../../../redis/redis.service"; +import { UserEntity } from "../../../core/entities/user.entity"; +import { UserRO } from "../../../core/interfaces/user.interface"; import { UserService } from "../../services/user.service"; import { FindUserById } from "../impl"; @@ -20,17 +20,17 @@ export class FindUserByIdHandler implements IQueryHandler { ) {} async execute({ id }: FindUserById): Promise { - const _user = await this.redisCacheService.get(id.toString()); + let user: UserEntity; - if (_user) { - return this.userService.buildUserRO(_user); - } - - const user = await this.userRepository.findOne(id); + user = (await this.redisCacheService.get(id.toString())) as UserEntity; if (!user) { - const errors = { User: " not found" }; - throw new HttpException({ errors }, 401); + user = await this.userRepository.findOne(id); + + if (!user) { + const errors = { User: "User not found" }; + throw new HttpException({ errors }, HttpStatus.NOT_FOUND); + } } return this.userService.buildUserRO(user); diff --git a/src/user/queries/handlers/index.ts b/src/user/application/queries/handlers/index.ts similarity index 100% rename from src/user/queries/handlers/index.ts rename to src/user/application/queries/handlers/index.ts diff --git a/src/user/queries/handlers/login.handler.ts b/src/user/application/queries/handlers/login.handler.ts similarity index 80% rename from src/user/queries/handlers/login.handler.ts rename to src/user/application/queries/handlers/login.handler.ts index 540e26f..d12061d 100644 --- a/src/user/queries/handlers/login.handler.ts +++ b/src/user/application/queries/handlers/login.handler.ts @@ -3,14 +3,14 @@ import { QueryHandler, IQueryHandler } from "@nestjs/cqrs"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import * as argon2 from "argon2"; -import { READ_CONNECTION } from "../../../config"; -import { LoginUserDto } from "../../dto"; -import { UserEntity } from "../../core/entities/user.entity"; -import { UserRO } from "../../core/interfaces/user.interface"; +import { READ_CONNECTION } from "../../../../configs"; +import { LoginUserDto } from "../../../core/dto"; +import { UserEntity } from "../../../core/entities/user.entity"; +import { UserRO } from "../../../core/interfaces/user.interface"; import { UserService } from "../../services/user.service"; import { LoginQuery } from "../impl"; -import { RedisService } from "../../../redis/redis.service"; -import { TIME_TO_LIVE } from "../../../redis/redis.constant"; +import { RedisService } from "../../../../redis/redis.service"; +import { TIME_TO_LIVE } from "../../../../redis/redis.constant"; @QueryHandler(LoginQuery) export class LoginQueryHandler implements IQueryHandler { diff --git a/src/user/queries/impl/find-user-by-email.query.ts b/src/user/application/queries/impl/find-user-by-email.query.ts similarity index 100% rename from src/user/queries/impl/find-user-by-email.query.ts rename to src/user/application/queries/impl/find-user-by-email.query.ts diff --git a/src/user/queries/impl/find-user-by-id.query.ts b/src/user/application/queries/impl/find-user-by-id.query.ts similarity index 100% rename from src/user/queries/impl/find-user-by-id.query.ts rename to src/user/application/queries/impl/find-user-by-id.query.ts diff --git a/src/user/queries/impl/index.ts b/src/user/application/queries/impl/index.ts similarity index 100% rename from src/user/queries/impl/index.ts rename to src/user/application/queries/impl/index.ts diff --git a/src/user/queries/impl/login.query.ts b/src/user/application/queries/impl/login.query.ts similarity index 64% rename from src/user/queries/impl/login.query.ts rename to src/user/application/queries/impl/login.query.ts index 46b3ac4..3b61efe 100644 --- a/src/user/queries/impl/login.query.ts +++ b/src/user/application/queries/impl/login.query.ts @@ -1,4 +1,4 @@ -import { LoginUserDto } from "../../dto"; +import { LoginUserDto } from "../../../core/dto"; export class LoginQuery { constructor(public readonly loginUserDto: LoginUserDto) {} diff --git a/src/user/queries/index.ts b/src/user/application/queries/index.ts similarity index 100% rename from src/user/queries/index.ts rename to src/user/application/queries/index.ts diff --git a/src/user/queries/query.module.ts b/src/user/application/queries/query.module.ts similarity index 66% rename from src/user/queries/query.module.ts rename to src/user/application/queries/query.module.ts index 637aeb9..5dcdd40 100644 --- a/src/user/queries/query.module.ts +++ b/src/user/application/queries/query.module.ts @@ -1,12 +1,12 @@ import { Module } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; + import { QueryHandlers } from "."; -import { READ_CONNECTION } from "../../config"; -import { UserController } from "../user.controller"; -import { UserEntity } from "../core"; +import { READ_CONNECTION } from "../../../configs"; +import { UserEntity } from "../../core"; import { UserService } from "../services/user.service"; -import { RedisModule } from "../../redis/redis.module"; +import { RedisModule } from "../../../redis/redis.module"; @Module({ imports: [ @@ -15,7 +15,7 @@ import { RedisModule } from "../../redis/redis.module"; RedisModule, ], providers: [UserService, ...QueryHandlers], - controllers: [UserController], + controllers: [], exports: [UserService], }) export class QueryModule {} diff --git a/src/user/services/auth.service.ts b/src/user/application/services/auth.service.ts similarity index 100% rename from src/user/services/auth.service.ts rename to src/user/application/services/auth.service.ts diff --git a/src/user/application/services/index.ts b/src/user/application/services/index.ts new file mode 100644 index 0000000..f4847dc --- /dev/null +++ b/src/user/application/services/index.ts @@ -0,0 +1,2 @@ +export * from "./auth.service"; +export * from "./user.service"; diff --git a/src/user/services/user.service.ts b/src/user/application/services/user.service.ts similarity index 83% rename from src/user/services/user.service.ts rename to src/user/application/services/user.service.ts index 6da4b43..1a60a33 100644 --- a/src/user/services/user.service.ts +++ b/src/user/application/services/user.service.ts @@ -1,8 +1,9 @@ import { Injectable } from "@nestjs/common"; -import { SECRET } from "../../config"; -import { UserEntity } from "../core/entities/user.entity"; const jwt = require("jsonwebtoken"); +import { JWT_SECRET_KEY } from "../../../configs"; +import { UserEntity } from "../../core/entities/user.entity"; + @Injectable() export class UserService { constructor() {} @@ -19,7 +20,7 @@ export class UserService { email: user.email, exp: exp.getTime() / 1000, }, - SECRET + JWT_SECRET_KEY ); } diff --git a/src/user/dto/create-user.dto.ts b/src/user/core/dto/create-user.dto.ts similarity index 100% rename from src/user/dto/create-user.dto.ts rename to src/user/core/dto/create-user.dto.ts diff --git a/src/user/dto/index.ts b/src/user/core/dto/index.ts similarity index 100% rename from src/user/dto/index.ts rename to src/user/core/dto/index.ts diff --git a/src/user/dto/login-user.dto.ts b/src/user/core/dto/login-user.dto.ts similarity index 100% rename from src/user/dto/login-user.dto.ts rename to src/user/core/dto/login-user.dto.ts diff --git a/src/user/dto/update-user.dto.ts b/src/user/core/dto/update-user.dto.ts similarity index 100% rename from src/user/dto/update-user.dto.ts rename to src/user/core/dto/update-user.dto.ts diff --git a/src/user/core/enums/user.enum.ts b/src/user/core/enums/user.enum.ts index e2678b3..bb5c49f 100644 --- a/src/user/core/enums/user.enum.ts +++ b/src/user/core/enums/user.enum.ts @@ -1,4 +1,4 @@ -export enum MessageType { +export enum MessageCmd { USER_CREATED = "USER_CREATED", USER_UPDATED = "USER_UPDATED", } diff --git a/src/user/core/interfaces/index.ts b/src/user/core/interfaces/index.ts index 868c20d..c17f532 100644 --- a/src/user/core/interfaces/index.ts +++ b/src/user/core/interfaces/index.ts @@ -1,2 +1 @@ export * from "./user.interface"; -export * from "./projection.interface"; diff --git a/src/user/core/interfaces/projection.interface.ts b/src/user/core/interfaces/projection.interface.ts deleted file mode 100644 index f6f6d03..0000000 --- a/src/user/core/interfaces/projection.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { UserEntity } from "../entities"; -import { MessageType } from "../enums"; - -export interface IProjection { - handle(): Promise; -} - -export interface IMessage { - type: MessageType; - payload: { - user?: UserEntity; - }; -} diff --git a/src/user/core/interfaces/user.interface.ts b/src/user/core/interfaces/user.interface.ts index 70145a5..0d3473c 100644 --- a/src/user/core/interfaces/user.interface.ts +++ b/src/user/core/interfaces/user.interface.ts @@ -1,5 +1,6 @@ import { ArticleEntity } from "../../../article/core/entities/article.entity"; import { CommentEntity } from "../../../article/core/entities/comment.entity"; +import { UserEntity } from "../entities"; export interface UserData { username: string; @@ -32,3 +33,9 @@ export interface IUser { articles?: ArticleEntity[]; comments?: CommentEntity[]; } + +export interface IPayloadUserRmq { + user: UserEntity; +} +export interface IPayloadUserCreated extends IPayloadUserRmq {} +export interface IPayloadUserUpdated extends IPayloadUserRmq {} diff --git a/src/user/presentation/rest/index.ts b/src/user/presentation/rest/index.ts new file mode 100644 index 0000000..5815124 --- /dev/null +++ b/src/user/presentation/rest/index.ts @@ -0,0 +1 @@ +export * from "./user.controller"; diff --git a/src/user/user.controller.ts b/src/user/presentation/rest/user.controller.ts similarity index 77% rename from src/user/user.controller.ts rename to src/user/presentation/rest/user.controller.ts index c5bc675..88f9186 100644 --- a/src/user/user.controller.ts +++ b/src/user/presentation/rest/user.controller.ts @@ -1,12 +1,16 @@ import { Body, Controller, Get, Post, Put, UsePipes } from "@nestjs/common"; import { CommandBus, QueryBus } from "@nestjs/cqrs"; import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from "@nestjs/swagger"; -import { ValidationPipe } from "../shared/pipes/validation.pipe"; -import { CreateUserDto, LoginUserDto, UpdateUserDto } from "./dto"; -import { CreateUserCommand, UpdateUserCommand } from "./commands"; -import { FindUserByEmailQuery, FindUserById, LoginQuery } from "./queries"; -import { User } from "./user.decorator"; -import { UserRO } from "./core/interfaces/user.interface"; + +import { User } from "../../../shared/middleware/user.decorator"; +import { ValidationPipe } from "../../../shared/pipes/validation.pipe"; +import { + CreateUserCommand, + UpdateUserCommand, +} from "../../application/commands"; +import { FindUserById, LoginQuery } from "../../application/queries"; +import { CreateUserDto, LoginUserDto, UpdateUserDto } from "../../core/dto"; +import { UserRO } from "../../core/interfaces/user.interface"; @ApiBearerAuth() @ApiTags("user") diff --git a/src/user/presentation/rmq/index.ts b/src/user/presentation/rmq/index.ts new file mode 100644 index 0000000..42bb90e --- /dev/null +++ b/src/user/presentation/rmq/index.ts @@ -0,0 +1 @@ +export * from "./user.rmq"; diff --git a/src/user/presentation/rmq/user.rmq.ts b/src/user/presentation/rmq/user.rmq.ts new file mode 100644 index 0000000..8edb97d --- /dev/null +++ b/src/user/presentation/rmq/user.rmq.ts @@ -0,0 +1,25 @@ +import { Controller } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; +import { EventPattern, Payload, Transport } from "@nestjs/microservices"; + +import { UserCreatedEvent, UserUpdatedEvent } from "../../application/events"; +import { + IPayloadUserCreated, + IPayloadUserUpdated, + MessageCmd, +} from "../../core"; + +@Controller() +export class UserRmq { + constructor(private readonly eventBus: EventBus) {} + + @EventPattern({ cmd: MessageCmd.USER_CREATED }, Transport.RMQ) + async userCreated(@Payload() payload: IPayloadUserCreated) { + this.eventBus.publish(new UserCreatedEvent(payload.user)); + } + + @EventPattern({ cmd: MessageCmd.USER_UPDATED }, Transport.RMQ) + async userUpdated(@Payload() payload: IPayloadUserUpdated) { + this.eventBus.publish(new UserUpdatedEvent(payload.user)); + } +} diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 3095526..bb2b50d 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -5,15 +5,16 @@ import { RequestMethod, } from "@nestjs/common"; import { CqrsModule } from "@nestjs/cqrs"; + import { RabbitMqModule } from "../rabbitmq/rabbitmq.module"; -import { AuthMiddleware } from "./auth.middleware"; -import { CommandModule } from "./commands/command.module"; -import { EventModule } from "./events/event.module"; -import { QueryModule } from "./queries/query.module"; -import { UserController } from "./user.controller"; -import { UserProjection } from "./user.projection"; -import { UserService } from "./services/user.service"; import { RedisModule } from "../redis/redis.module"; +import { AuthMiddleware } from "../shared/middleware/auth.middleware"; +import { CommandModule } from "./application/commands/command.module"; +import { EventModule } from "./application/events/event.module"; +import { QueryModule } from "./application/queries/query.module"; +import { UserService } from "./application/services"; +import { UserController } from "./presentation/rest"; +import { UserRmq } from "./presentation/rmq"; @Module({ imports: [ @@ -24,8 +25,8 @@ import { RedisModule } from "../redis/redis.module"; RabbitMqModule, RedisModule, ], - providers: [UserService, UserProjection], - controllers: [UserController], + providers: [UserService], + controllers: [UserController, UserRmq], exports: [UserService], }) export class UserModule implements NestModule { diff --git a/src/user/user.projection.ts b/src/user/user.projection.ts deleted file mode 100644 index 0610047..0000000 --- a/src/user/user.projection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { EventBus } from "@nestjs/cqrs"; -import { ConsumerService } from "../rabbitmq/consumer.service"; -import { USER_QUEUE } from "../rabbitmq/rabbitmq.constants"; -import { IMessage, IProjection } from "./core"; -import { MessageType } from "./core/enums/user.enum"; -import { UserCreatedEvent, UserUpdatedEvent } from "./events"; - -@Injectable() -export class UserProjection implements IProjection { - constructor( - private readonly consumer: ConsumerService, - private readonly eventBus: EventBus - ) {} - - async handle() { - await this.consumer.consume(USER_QUEUE, (msg: IMessage) => { - this.handleMessage(msg); - }); - } - - private async handleMessage({ type, payload }: IMessage) { - switch (type) { - case MessageType.USER_CREATED: - this.eventBus.publish(new UserCreatedEvent(payload.user)); - break; - case MessageType.USER_UPDATED: - this.eventBus.publish(new UserUpdatedEvent(payload.user)); - break; - default: - break; - } - } -} diff --git a/yarn.lock b/yarn.lock index de06b43..4f74a1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -567,6 +567,15 @@ resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-0.4.1.tgz#e7fe038f0bdda7b8f858fa79ca8516b8f9069b1a" integrity sha512-JXrw2LMangSU3vnaXWXVX47GRG1FbbNh4aVBbidDjxT3zlghsoNQY6qyWtT001MCl8lJGo8I6i6+DurBRRxl/Q== +"@nestjs/microservices@7.2": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-7.2.0.tgz#d360c1c24087a8de2ac7b2d7efc20068d58e1394" + integrity sha512-uWBuM1F3LmKZAbfAmp3G1cvUKP+gUgLE7dXZMGZl+vaLPaO0+gX8z2fDhI/XJc6urywPJMPJZE2wDJcVec8OsA== + dependencies: + iterare "1.2.1" + json-socket "0.3.0" + tslib "2.0.0" + "@nestjs/platform-express@^7.0.5": version "7.6.18" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-7.6.18.tgz#cdf442dfd85948fc7b67bbc4007dddef83cdd4b9" @@ -889,6 +898,13 @@ ajv@^6.12.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +amqp-connection-manager@^4.1.14: + version "4.1.14" + resolved "https://registry.yarnpkg.com/amqp-connection-manager/-/amqp-connection-manager-4.1.14.tgz#603d20ffbc6d90fe464c2a08600c7644c9475342" + integrity sha512-1km47dIvEr0HhMUazqovSvNwIlSvDX2APdUpULaINtHpiki1O+cLRaTeXb/jav4OLtH+k6GBXx5gsKOT9kcGKQ== + dependencies: + promise-breaker "^6.0.0" + amqplib@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.3.tgz#e186a2f74521eb55ec54db6d25ae82c29c1f911a" @@ -3617,6 +3633,11 @@ json-schema@0.4.0: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== +json-socket@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/json-socket/-/json-socket-0.3.0.tgz#f4b953c685bb8e8bd0b72438f5208d9a0799ae07" + integrity sha512-jc8ZbUnYIWdxERFWQKVgwSLkGSe+kyzvmYxwNaRgx/c8NNyuHes4UHnPM3LUrAFXUx1BhNJ94n1h/KCRlbvV0g== + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4658,6 +4679,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +promise-breaker@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/promise-breaker/-/promise-breaker-6.0.0.tgz#107d2b70f161236abdb4ac5a736c7eb8df489d0f" + integrity sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -5691,6 +5717,11 @@ ts-node@^8.9.1: source-map-support "^0.5.17" yn "3.1.1" +tslib@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" + integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== + tslib@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" @@ -6109,9 +6140,9 @@ widest-line@^2.0.0: string-width "^2.1.1" word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== wrap-ansi@^6.2.0: version "6.2.0"