Skip to content

Commit 037a859

Browse files
committed
refactor: redesign commands into command pattern with previous commands implemented
1 parent cfece54 commit 037a859

File tree

7 files changed

+130
-101
lines changed

7 files changed

+130
-101
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { SendCodewarsLeaderboardToChannelInput } from "application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelInput";
2+
import { Command } from "../../types";
3+
import KataService from "../../domain/service/kataService/kataService";
4+
import ChatService from "../../domain/service/chatService";
5+
import KataLeaderboardUser from "../../domain/service/kataService/kataLeaderboardUser";
6+
7+
export default class CodewarsLeaderboardCommand implements Command {
8+
readonly name = "!cwl";
9+
10+
private chatService: ChatService;
11+
12+
private kataService: KataService;
13+
14+
constructor(chatService: ChatService, kataService: KataService) {
15+
this.chatService = chatService;
16+
this.kataService = kataService;
17+
}
18+
19+
private formatLeaderboard(leaderboard: KataLeaderboardUser[]): string {
20+
let output = "```";
21+
let position = 1;
22+
23+
const leaderboardTotalEntriesToShow = 10;
24+
const leaderboardEntries = leaderboard.slice(0, leaderboardTotalEntriesToShow);
25+
const leaderboardEntriesLeft = leaderboard.length - leaderboardTotalEntriesToShow;
26+
27+
leaderboardEntries.forEach((user: KataLeaderboardUser) => {
28+
const pointsCollection = user.getPoints().map((points: number) => points || 0);
29+
30+
output += `${position}. ${user.getUsername()} - ${user.getScore()} - [${pointsCollection.join(",")}] points
31+
`;
32+
33+
position += 1;
34+
});
35+
36+
output += "```";
37+
38+
if (leaderboardEntriesLeft > 1) {
39+
output += `
40+
... e ${leaderboardEntriesLeft} outras participações em https://codewars.devpt.co`;
41+
} else if (leaderboardEntriesLeft === 1) {
42+
output += `
43+
... e 1 outra participação em https://codewars.devpt.co`;
44+
}
45+
46+
return output;
47+
}
48+
49+
async execute({ channelId }: SendCodewarsLeaderboardToChannelInput): Promise<void> {
50+
const leaderboard = await this.kataService.getLeaderboard();
51+
52+
if (leaderboard.length === 0) {
53+
this.chatService.sendMessageToChannel("Ainda não existem participantes nesta ediçăo do desafio.", channelId);
54+
return;
55+
}
56+
57+
const formattedLeaderboard = this.formatLeaderboard(leaderboard);
58+
this.chatService.sendMessageToChannel(formattedLeaderboard, channelId);
59+
}
60+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Command, Context } from "../../types";
2+
import ChatService from "../../domain/service/chatService";
3+
4+
export default class DontAskToAskCommand implements Command {
5+
readonly name = "!ja";
6+
7+
private readonly message: string =
8+
"Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/";
9+
10+
private chatService: ChatService;
11+
12+
constructor(chatService: ChatService) {
13+
this.chatService = chatService;
14+
}
15+
16+
async execute(context: Context): Promise<void> {
17+
await this.chatService.sendMessageToChannel(this.message, context.channelId);
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Command, Context } from "../../types";
2+
import ChatService from "../../domain/service/chatService";
3+
4+
export default class OnlyCodeQuestionsCommand implements Command {
5+
readonly name = "!oc";
6+
7+
private chatService: ChatService;
8+
9+
private readonly message: string =
10+
":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:";
11+
12+
constructor(chatService: ChatService) {
13+
this.chatService = chatService;
14+
}
15+
16+
async execute(context: Context): Promise<void> {
17+
await this.chatService.sendMessageToChannel(this.message, context.channelId);
18+
}
19+
}
Lines changed: 17 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,44 @@
1-
import { promises as fs } from "fs";
2-
import path from "path";
3-
import { Context } from "../../types";
1+
import CodewarsLeaderboardCommand from "../../application/command/codewarsLeaderboardCommand";
2+
import DontAskToAskCommand from "../../application/command/dontAskToAskCommand";
3+
import OnlyCodeQuestionsCommand from "../../application/command/onlyCodeQuestionsCommand";
4+
import { Command, Context } from "../../types";
45
import UseCaseNotFound from "../exception/useCaseNotFound";
5-
import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase";
6-
import MessageRepository from "../repository/messageRepository";
76
import ChatService from "./chatService";
8-
import LoggerService from "./loggerService";
9-
import ChannelResolver from "./channelResolver";
107
import KataService from "./kataService/kataService";
11-
import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase";
8+
import LoggerService from "./loggerService";
129

1310
export default class CommandUseCaseResolver {
14-
private messageRepository: MessageRepository;
15-
16-
private chatService: ChatService;
11+
private commands: Command[] = [];
1712

1813
private loggerService: LoggerService;
1914

20-
private channelResolver: ChannelResolver;
21-
22-
private kataService: KataService;
23-
24-
private commandMessages: Record<string, string> = {};
25-
2615
constructor({
27-
messageRepository,
2816
chatService,
29-
loggerService,
30-
channelResolver,
3117
kataService,
18+
loggerService,
3219
}: {
33-
messageRepository: MessageRepository;
3420
chatService: ChatService;
35-
loggerService: LoggerService;
36-
channelResolver: ChannelResolver;
3721
kataService: KataService;
22+
loggerService: LoggerService;
3823
}) {
39-
this.messageRepository = messageRepository;
40-
this.chatService = chatService;
4124
this.loggerService = loggerService;
42-
this.channelResolver = channelResolver;
43-
this.kataService = kataService;
44-
}
4525

46-
private async loadCommands(): Promise<void> {
47-
const filePath = path.join(__dirname, "commands.json");
48-
const data = await fs.readFile(filePath, "utf-8");
49-
this.commandMessages = JSON.parse(data);
26+
this.commands.push(
27+
new CodewarsLeaderboardCommand(chatService, kataService),
28+
new DontAskToAskCommand(chatService),
29+
new OnlyCodeQuestionsCommand(chatService)
30+
);
5031
}
5132

5233
async resolveByCommand(command: string, context: Context): Promise<void> {
5334
this.loggerService.log(`Command received: "${command}"`);
5435

55-
const deps = {
56-
messageRepository: this.messageRepository,
57-
chatService: this.chatService,
58-
loggerService: this.loggerService,
59-
channelResolver: this.channelResolver,
60-
kataService: this.kataService,
61-
};
62-
63-
if (Object.keys(this.commandMessages).length === 0) {
64-
await this.loadCommands();
65-
}
66-
67-
if (this.commandMessages[command]) {
68-
new SendMessageToChannelUseCase(deps).execute({
69-
channelId: context.channelId,
70-
message: this.commandMessages[command],
71-
});
72-
return;
73-
}
36+
const commandInstance = this.commands.find((cmd) => cmd.name === command);
7437

75-
if (command === "!cwl") {
76-
new SendCodewarsLeaderboardToChannelUseCase(deps).execute({
77-
channelId: context.channelId,
78-
});
79-
return;
38+
if (!commandInstance) {
39+
throw new UseCaseNotFound().byCommand(command);
8040
}
8141

82-
throw new UseCaseNotFound().byCommand(command);
42+
await commandInstance.execute(context);
8343
}
8444
}

domain/service/commands.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ export enum ChannelSlug {
1010
ENTRANCE = "ENTRANCE",
1111
JOBS = "JOBS",
1212
}
13+
14+
export type CommandMessages = {
15+
[command: string]: string;
16+
};
17+
18+
export interface Command {
19+
name: string;
20+
execute(context: Context): Promise<void>;
21+
}

vitest/service/commandUseCaseResolver.spec.ts

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,41 @@
11
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
2-
import { promises as fs } from "fs";
32
import CommandUseCaseResolver from "../../domain/service/commandUseCaseResolver";
4-
import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase";
5-
import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase";
63
import { Context } from "../../types";
74

8-
vi.mock("../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase");
9-
vi.mock("../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase");
10-
11-
vi.mock("fs", () => ({
12-
promises: {
13-
readFile: vi.fn(),
14-
},
15-
}));
16-
175
describe("CommandUseCaseResolver", () => {
186
let commandUseCaseResolver: CommandUseCaseResolver;
197
const mockContext: Context = {
208
channelId: "test-channel",
219
};
2210

2311
beforeEach(async () => {
24-
(fs.readFile as vi.Mock).mockResolvedValueOnce(
25-
JSON.stringify({
26-
"!ja": "Olá! Test message",
27-
"!oc": ":warning: Only Code questions!",
28-
})
29-
);
30-
3112
commandUseCaseResolver = new CommandUseCaseResolver({
32-
messageRepository: {
33-
getRandomIntroMessage: vi.fn(),
34-
getRandomWelcomingMessage: vi.fn(),
35-
},
3613
chatService: {
3714
sendMessageToChannel: vi.fn(),
3815
},
3916
loggerService: { log: vi.fn() },
40-
channelResolver: {
41-
getBySlug: vi.fn(),
42-
},
4317
kataService: {
4418
getLeaderboard: vi.fn().mockResolvedValue([]),
4519
},
4620
});
4721
});
4822

49-
it("should send the correct message for !ja command", async () => {
23+
it("should invoke for !ja command", async () => {
5024
await commandUseCaseResolver.resolveByCommand("!ja", mockContext);
5125

52-
expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({
53-
channelId: "test-channel",
54-
message: "Olá! Test message",
55-
});
26+
expect(() => commandUseCaseResolver.resolveByCommand("!ja", mockContext)).not.toThrow();
5627
});
5728

58-
it("should send the correct message for !oc command", async () => {
29+
it("should invoke for !oc command", async () => {
5930
await commandUseCaseResolver.resolveByCommand("!oc", mockContext);
6031

61-
expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({
62-
channelId: "test-channel",
63-
message: ":warning: Only Code questions!",
64-
});
32+
expect(() => commandUseCaseResolver.resolveByCommand("!oc", mockContext)).not.toThrow();
6533
});
6634

67-
it("should handle the !cwl command by sending the leaderboard", async () => {
35+
it("should invoke for !cwl command", async () => {
6836
await commandUseCaseResolver.resolveByCommand("!cwl", mockContext);
6937

70-
expect(SendCodewarsLeaderboardToChannelUseCase.prototype.execute).toHaveBeenCalledWith({
71-
channelId: "test-channel",
72-
});
38+
expect(() => commandUseCaseResolver.resolveByCommand("!cwl", mockContext)).not.toThrow();
7339
});
7440

7541
it("should throw UseCaseNotFound error for unknown command", async () => {

0 commit comments

Comments
 (0)