From 037a8599cdf10d8ed3432be099a7a6e05d71d610 Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 2 Nov 2024 15:54:43 +0000 Subject: [PATCH] refactor: redesign commands into command pattern with previous commands implemented --- .../command/codewarsLeaderboardCommand.ts | 60 +++++++++++++++ application/command/dontAskToAskCommand.ts | 19 +++++ .../command/onlyCodeQuestionsCommand.ts | 19 +++++ domain/service/commandUseCaseResolver.ts | 74 +++++-------------- domain/service/commands.json | 4 - types.ts | 9 +++ vitest/service/commandUseCaseResolver.spec.ts | 46 ++---------- 7 files changed, 130 insertions(+), 101 deletions(-) create mode 100644 application/command/codewarsLeaderboardCommand.ts create mode 100644 application/command/dontAskToAskCommand.ts create mode 100644 application/command/onlyCodeQuestionsCommand.ts delete mode 100644 domain/service/commands.json diff --git a/application/command/codewarsLeaderboardCommand.ts b/application/command/codewarsLeaderboardCommand.ts new file mode 100644 index 0000000..91966b6 --- /dev/null +++ b/application/command/codewarsLeaderboardCommand.ts @@ -0,0 +1,60 @@ +import { SendCodewarsLeaderboardToChannelInput } from "application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelInput"; +import { Command } from "../../types"; +import KataService from "../../domain/service/kataService/kataService"; +import ChatService from "../../domain/service/chatService"; +import KataLeaderboardUser from "../../domain/service/kataService/kataLeaderboardUser"; + +export default class CodewarsLeaderboardCommand implements Command { + readonly name = "!cwl"; + + private chatService: ChatService; + + private kataService: KataService; + + constructor(chatService: ChatService, kataService: KataService) { + this.chatService = chatService; + this.kataService = kataService; + } + + private formatLeaderboard(leaderboard: KataLeaderboardUser[]): string { + let output = "```"; + let position = 1; + + const leaderboardTotalEntriesToShow = 10; + const leaderboardEntries = leaderboard.slice(0, leaderboardTotalEntriesToShow); + const leaderboardEntriesLeft = leaderboard.length - leaderboardTotalEntriesToShow; + + leaderboardEntries.forEach((user: KataLeaderboardUser) => { + const pointsCollection = user.getPoints().map((points: number) => points || 0); + + output += `${position}. ${user.getUsername()} - ${user.getScore()} - [${pointsCollection.join(",")}] points +`; + + position += 1; + }); + + output += "```"; + + if (leaderboardEntriesLeft > 1) { + output += ` +... e ${leaderboardEntriesLeft} outras participações em https://codewars.devpt.co`; + } else if (leaderboardEntriesLeft === 1) { + output += ` +... e 1 outra participação em https://codewars.devpt.co`; + } + + return output; + } + + async execute({ channelId }: SendCodewarsLeaderboardToChannelInput): Promise { + const leaderboard = await this.kataService.getLeaderboard(); + + if (leaderboard.length === 0) { + this.chatService.sendMessageToChannel("Ainda não existem participantes nesta ediçăo do desafio.", channelId); + return; + } + + const formattedLeaderboard = this.formatLeaderboard(leaderboard); + this.chatService.sendMessageToChannel(formattedLeaderboard, channelId); + } +} diff --git a/application/command/dontAskToAskCommand.ts b/application/command/dontAskToAskCommand.ts new file mode 100644 index 0000000..2a8d439 --- /dev/null +++ b/application/command/dontAskToAskCommand.ts @@ -0,0 +1,19 @@ +import { Command, Context } from "../../types"; +import ChatService from "../../domain/service/chatService"; + +export default class DontAskToAskCommand implements Command { + readonly name = "!ja"; + + private readonly message: string = + "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/"; + + private chatService: ChatService; + + constructor(chatService: ChatService) { + this.chatService = chatService; + } + + async execute(context: Context): Promise { + await this.chatService.sendMessageToChannel(this.message, context.channelId); + } +} diff --git a/application/command/onlyCodeQuestionsCommand.ts b/application/command/onlyCodeQuestionsCommand.ts new file mode 100644 index 0000000..2aea596 --- /dev/null +++ b/application/command/onlyCodeQuestionsCommand.ts @@ -0,0 +1,19 @@ +import { Command, Context } from "../../types"; +import ChatService from "../../domain/service/chatService"; + +export default class OnlyCodeQuestionsCommand implements Command { + readonly name = "!oc"; + + private chatService: ChatService; + + private readonly message: string = + ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:"; + + constructor(chatService: ChatService) { + this.chatService = chatService; + } + + async execute(context: Context): Promise { + await this.chatService.sendMessageToChannel(this.message, context.channelId); + } +} diff --git a/domain/service/commandUseCaseResolver.ts b/domain/service/commandUseCaseResolver.ts index 000a8fc..ab1e8d1 100644 --- a/domain/service/commandUseCaseResolver.ts +++ b/domain/service/commandUseCaseResolver.ts @@ -1,84 +1,44 @@ -import { promises as fs } from "fs"; -import path from "path"; -import { Context } from "../../types"; +import CodewarsLeaderboardCommand from "../../application/command/codewarsLeaderboardCommand"; +import DontAskToAskCommand from "../../application/command/dontAskToAskCommand"; +import OnlyCodeQuestionsCommand from "../../application/command/onlyCodeQuestionsCommand"; +import { Command, Context } from "../../types"; import UseCaseNotFound from "../exception/useCaseNotFound"; -import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; -import MessageRepository from "../repository/messageRepository"; import ChatService from "./chatService"; -import LoggerService from "./loggerService"; -import ChannelResolver from "./channelResolver"; import KataService from "./kataService/kataService"; -import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; +import LoggerService from "./loggerService"; export default class CommandUseCaseResolver { - private messageRepository: MessageRepository; - - private chatService: ChatService; + private commands: Command[] = []; private loggerService: LoggerService; - private channelResolver: ChannelResolver; - - private kataService: KataService; - - private commandMessages: Record = {}; - constructor({ - messageRepository, chatService, - loggerService, - channelResolver, kataService, + loggerService, }: { - messageRepository: MessageRepository; chatService: ChatService; - loggerService: LoggerService; - channelResolver: ChannelResolver; kataService: KataService; + loggerService: LoggerService; }) { - this.messageRepository = messageRepository; - this.chatService = chatService; this.loggerService = loggerService; - this.channelResolver = channelResolver; - this.kataService = kataService; - } - private async loadCommands(): Promise { - const filePath = path.join(__dirname, "commands.json"); - const data = await fs.readFile(filePath, "utf-8"); - this.commandMessages = JSON.parse(data); + this.commands.push( + new CodewarsLeaderboardCommand(chatService, kataService), + new DontAskToAskCommand(chatService), + new OnlyCodeQuestionsCommand(chatService) + ); } async resolveByCommand(command: string, context: Context): Promise { this.loggerService.log(`Command received: "${command}"`); - const deps = { - messageRepository: this.messageRepository, - chatService: this.chatService, - loggerService: this.loggerService, - channelResolver: this.channelResolver, - kataService: this.kataService, - }; - - if (Object.keys(this.commandMessages).length === 0) { - await this.loadCommands(); - } - - if (this.commandMessages[command]) { - new SendMessageToChannelUseCase(deps).execute({ - channelId: context.channelId, - message: this.commandMessages[command], - }); - return; - } + const commandInstance = this.commands.find((cmd) => cmd.name === command); - if (command === "!cwl") { - new SendCodewarsLeaderboardToChannelUseCase(deps).execute({ - channelId: context.channelId, - }); - return; + if (!commandInstance) { + throw new UseCaseNotFound().byCommand(command); } - throw new UseCaseNotFound().byCommand(command); + await commandInstance.execute(context); } } diff --git a/domain/service/commands.json b/domain/service/commands.json deleted file mode 100644 index 38ec8a9..0000000 --- a/domain/service/commands.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "!ja": "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/", - "!oc": ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:" -} diff --git a/types.ts b/types.ts index e8c17f9..5e7f2bf 100644 --- a/types.ts +++ b/types.ts @@ -10,3 +10,12 @@ export enum ChannelSlug { ENTRANCE = "ENTRANCE", JOBS = "JOBS", } + +export type CommandMessages = { + [command: string]: string; +}; + +export interface Command { + name: string; + execute(context: Context): Promise; +} diff --git a/vitest/service/commandUseCaseResolver.spec.ts b/vitest/service/commandUseCaseResolver.spec.ts index 3bdd54f..913eb7c 100644 --- a/vitest/service/commandUseCaseResolver.spec.ts +++ b/vitest/service/commandUseCaseResolver.spec.ts @@ -1,19 +1,7 @@ import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; -import { promises as fs } from "fs"; import CommandUseCaseResolver from "../../domain/service/commandUseCaseResolver"; -import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; -import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; import { Context } from "../../types"; -vi.mock("../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"); -vi.mock("../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"); - -vi.mock("fs", () => ({ - promises: { - readFile: vi.fn(), - }, -})); - describe("CommandUseCaseResolver", () => { let commandUseCaseResolver: CommandUseCaseResolver; const mockContext: Context = { @@ -21,55 +9,33 @@ describe("CommandUseCaseResolver", () => { }; beforeEach(async () => { - (fs.readFile as vi.Mock).mockResolvedValueOnce( - JSON.stringify({ - "!ja": "Olá! Test message", - "!oc": ":warning: Only Code questions!", - }) - ); - commandUseCaseResolver = new CommandUseCaseResolver({ - messageRepository: { - getRandomIntroMessage: vi.fn(), - getRandomWelcomingMessage: vi.fn(), - }, chatService: { sendMessageToChannel: vi.fn(), }, loggerService: { log: vi.fn() }, - channelResolver: { - getBySlug: vi.fn(), - }, kataService: { getLeaderboard: vi.fn().mockResolvedValue([]), }, }); }); - it("should send the correct message for !ja command", async () => { + it("should invoke for !ja command", async () => { await commandUseCaseResolver.resolveByCommand("!ja", mockContext); - expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - message: "Olá! Test message", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!ja", mockContext)).not.toThrow(); }); - it("should send the correct message for !oc command", async () => { + it("should invoke for !oc command", async () => { await commandUseCaseResolver.resolveByCommand("!oc", mockContext); - expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - message: ":warning: Only Code questions!", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!oc", mockContext)).not.toThrow(); }); - it("should handle the !cwl command by sending the leaderboard", async () => { + it("should invoke for !cwl command", async () => { await commandUseCaseResolver.resolveByCommand("!cwl", mockContext); - expect(SendCodewarsLeaderboardToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!cwl", mockContext)).not.toThrow(); }); it("should throw UseCaseNotFound error for unknown command", async () => {