Skip to content

Commit

Permalink
Merge pull request #40 from devpt-org/add/tests
Browse files Browse the repository at this point in the history
Refactor de arquitetura, implementação de UseCases e adição de testes
  • Loading branch information
IvoPereira authored Sep 26, 2022
2 parents 1b1f127 + 103eb4f commit b85ddb9
Show file tree
Hide file tree
Showing 24 changed files with 8,361 additions and 117 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
30 changes: 25 additions & 5 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
{
"extends": ["airbnb", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"],
"no-console": 0
"env": {
"browser": true,
"es2021": true
},
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["prettier", "@typescript-eslint"],
"settings": {
"react": {
// https://github.com/DRD4-7R/eslint-config-7r-building/issues/1
"version": "999.999.999"
}
},
"rules": {
"prettier/prettier": ["error"],
"no-console": 0,
"class-methods-use-this": 0
}
}
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx pretty-quick --staged && npm exec concurrently npm:test npm:lint
npx pretty-quick --staged && npm exec concurrently npm:test:once npm:lint
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
worker: npm start
worker: npm run build && npm start
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface SendMessageToChannelInput {
message: string;
channelId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import ChatService from "../../../domain/service/chatService";
import { SendMessageToChannelInput } from "./sendMessageToChannelInput";

export default class SendMessageToChannelUseCase {
private chatService: ChatService;

constructor({ chatService }: { chatService: ChatService }) {
this.chatService = chatService;
}

async execute({ message, channelId }: SendMessageToChannelInput): Promise<void> {
await this.chatService.sendMessageToChannel(message, channelId);
}
}
49 changes: 49 additions & 0 deletions application/usecases/sendWelcomeMessageUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import LoggerService from "../../domain/service/loggerService";
import MessageRepository from "../../domain/repository/messageRepository";
import ChatService from "../../domain/service/chatService";
import { ChatMember, ChannelSlug } from "../../types";
import ChannelResolver from "../../domain/service/channelResolver";

const replacePlaceholders = (phrases: string, memberID: string) => {
const memberIdTag = `<@${memberID}>`;
return phrases.replace("{MEMBER_ID}", memberIdTag);
};

export default class SendWelcomeMessageUseCase {
private messageRepository: MessageRepository;

private chatService: ChatService;

private loggerService: LoggerService;

private channelResolver: ChannelResolver;

constructor({
messageRepository,
chatService,
loggerService,
channelResolver,
}: {
messageRepository: MessageRepository;
chatService: ChatService;
loggerService: LoggerService;
channelResolver: ChannelResolver;
}) {
this.messageRepository = messageRepository;
this.chatService = chatService;
this.loggerService = loggerService;
this.channelResolver = channelResolver;
}

async execute(member: ChatMember): Promise<void> {
this.loggerService.log("Member joined the server!");

const introPhrase = this.messageRepository.getRandomIntroMessage();
const welcomingPhrase = this.messageRepository.getRandomWelcomingMessage();
const finalPhrase = replacePlaceholders(`${introPhrase}${welcomingPhrase}`, member.id);

this.loggerService.log(`[NEW JOIN] ${finalPhrase}`);

this.chatService.sendMessageToChannel(finalPhrase, this.channelResolver.getBySlug(ChannelSlug.ENTRANCE));
}
}
4 changes: 2 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3'
version: "3"

services:
app:
Expand All @@ -7,4 +7,4 @@ services:
dockerfile: Dockerfile.dev
volumes:
- ./:/usr/src/welcome_bot:cached
- /usr/src/welcome_bot/node_modules
- /usr/src/welcome_bot/node_modules
5 changes: 5 additions & 0 deletions domain/exception/useCaseNotFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default class UseCaseNotFound extends Error {
byCommand(command: string) {
return new UseCaseNotFound(`Use case for command "${command}" not found`);
}
}
4 changes: 4 additions & 0 deletions domain/repository/messageRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface MessageRepository {
getRandomIntroMessage(): string;
getRandomWelcomingMessage(): string;
}
18 changes: 18 additions & 0 deletions domain/service/channelResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ChannelSlug } from "../../types";

const fallbackChannelIds: Record<ChannelSlug, string> = {
[ChannelSlug.ENTRANCE]: "855861944930402344",
[ChannelSlug.JOBS]: "876826576749215744",
};

export default class ChannelResolver {
getBySlug(slug: ChannelSlug): string {
const channelId = process.env[`CHANNEL_${slug}`] || fallbackChannelIds[slug];

if (!channelId) {
throw new Error(`Channel ID for "${slug}" not found`);
}

return channelId;
}
}
3 changes: 3 additions & 0 deletions domain/service/chatService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default interface ChatService {
sendMessageToChannel(message: string, channelId: string): void;
}
66 changes: 66 additions & 0 deletions domain/service/commandUseCaseResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { 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";

type CallbackFunctionVariadic = (...args: unknown[]) => void;

export default class CommandUseCaseResolver {
private messageRepository: MessageRepository;

private chatService: ChatService;

private loggerService: LoggerService;

private channelResolver: ChannelResolver;

constructor({
messageRepository,
chatService,
loggerService,
channelResolver,
}: {
messageRepository: MessageRepository;
chatService: ChatService;
loggerService: LoggerService;
channelResolver: ChannelResolver;
}) {
this.messageRepository = messageRepository;
this.chatService = chatService;
this.loggerService = loggerService;
this.channelResolver = channelResolver;
}

resolveByCommand(command: string, context: Context): void {
this.loggerService.log(`Command received: "${command}"`);

const deps = {
messageRepository: this.messageRepository,
chatService: this.chatService,
loggerService: this.loggerService,
channelResolver: this.channelResolver,
};

const commandUseCases: Record<string, CallbackFunctionVariadic> = {
"!ja": async () =>
new SendMessageToChannelUseCase(deps).execute({
channelId: context.channelId,
message: ":point_right: https://dontasktoask.com/pt-pt/",
}),
"!oc": async () =>
new SendMessageToChannelUseCase(deps).execute({
channelId: context.channelId,
message: ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:",
}),
};

if (!commandUseCases[command]) {
throw new UseCaseNotFound().byCommand(command);
}

commandUseCases[command]();
}
}
3 changes: 3 additions & 0 deletions domain/service/loggerService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default interface LoggerService {
log(...args: unknown[]): void;
}
97 changes: 45 additions & 52 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,59 @@
import { GuildMember, Message, Client, Intents, TextChannel } from "discord.js";
import introPhrases from "./assets/phrases/intro.json";
import welcomingPhrases from "./assets/phrases/welcoming.json";
import "dotenv/config.js";

interface Phrases {
intro: Array<string>;
welcoming: Array<string>;
}

// #geral
const DESTINATION_CHANNEL_ID: string = process.env.DESTINATION_CHANNEL_ID
? process.env.DESTINATION_CHANNEL_ID
: "855861944930402344";
// #comandos-testes
// const DESTINATION_CHANNEL_ID = '807190194268012554';

const config = { phrases: {} as Phrases };
config.phrases.intro = introPhrases;
config.phrases.welcoming = welcomingPhrases;
import { GuildMember, Message, Client, Intents } from "discord.js";
import * as dotenv from "dotenv";
import SendWelcomeMessageUseCase from "./application/usecases/sendWelcomeMessageUseCase";
import FileMessageRepository from "./infrastructure/repository/fileMessageRepository";
import ChatService from "./domain/service/chatService";
import DiscordChatService from "./infrastructure/service/discordChatService";
import ConsoleLoggerService from "./infrastructure/service/consoleLoggerService";
import MessageRepository from "./domain/repository/messageRepository";
import LoggerService from "./domain/service/loggerService";
import CommandUseCaseResolver from "./domain/service/commandUseCaseResolver";
import ChannelResolver from "./domain/service/channelResolver";

dotenv.config();

const { DISCORD_TOKEN } = process.env;

const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_MESSAGES],
});
client.once("ready", () => console.log("Ready!"));
client.login(process.env.DISCORD_TOKEN);

const getRandomStringFromCollection = (collection: Array<string>) =>
collection[Math.floor(Math.random() * collection.length)].trim();
const messageRepository: MessageRepository = new FileMessageRepository();
const chatService: ChatService = new DiscordChatService(client);
const loggerService: LoggerService = new ConsoleLoggerService();
const channelResolver: ChannelResolver = new ChannelResolver();
const useCaseResolver = new CommandUseCaseResolver({
messageRepository,
chatService,
loggerService,
channelResolver,
});

const replacePlaceholders = (phrases: string, memberID: string) => {
const memberIdTag = `<@${memberID}>`;
return phrases.replace("{MEMBER_ID}", memberIdTag);
};
client.once("ready", () => loggerService.log("Ready!"));

const welcome = (memberID: string) => {
const introPhrase = getRandomStringFromCollection(config.phrases.intro).trim();
const welcomingPhrase = getRandomStringFromCollection(config.phrases.welcoming).trim();
const finalPhrase = replacePlaceholders(`${introPhrase}${welcomingPhrase}`, memberID);
console.log(`[NEW JOIN] ${finalPhrase}`);
return finalPhrase;
};
client.login(DISCORD_TOKEN);

client.on("guildMemberAdd", async (member: GuildMember) => {
const message = await welcome(member.id);
const textChannelData = member.guild.channels.cache.get(DESTINATION_CHANNEL_ID);
client.on("guildMemberAdd", (member: GuildMember) =>
new SendWelcomeMessageUseCase({
messageRepository,
chatService,
loggerService,
channelResolver,
}).execute(member)
);

console.log("Member joined the server!");
if (!textChannelData)
return console.log("There was an error finding the text channel data, cannot send welcome message.");
client.on("messageCreate", (messages: Message) => {
const COMMAND_PREFIX = "!";

(textChannelData as TextChannel).send(message);
});
if (!messages.content.startsWith(COMMAND_PREFIX)) return;

client.on("messageCreate", (messages: Message) => {
switch (messages.content) {
case "!ja":
messages.channel.send(":point_right: https://dontasktoask.com/pt-pt/");
break;
case "!oc":
messages.channel.send(":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:");
break;
const command = messages.content.split(" ")[0];

// no default
try {
useCaseResolver.resolveByCommand(command, {
channelId: messages.channel.id,
});
} catch (error: unknown) {
loggerService.log(error);
}
});
21 changes: 21 additions & 0 deletions infrastructure/repository/fileMessageRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import intro from "../../assets/phrases/intro.json";
import welcoming from "../../assets/phrases/welcoming.json";
import MessageRepository from "../../domain/repository/messageRepository";

interface Phrases {
intro: Array<string>;
welcoming: Array<string>;
}

const config = { phrases: {} as Phrases };
config.phrases.intro = intro;
config.phrases.welcoming = welcoming;

const getRandomStringFromCollection = (collection: Array<string>) =>
collection[Math.floor(Math.random() * collection.length)].trim();

export default class FileMessageRepository implements MessageRepository {
getRandomIntroMessage = () => getRandomStringFromCollection(config.phrases.intro).trim();

getRandomWelcomingMessage = () => getRandomStringFromCollection(config.phrases.welcoming).trim();
}
7 changes: 7 additions & 0 deletions infrastructure/service/consoleLoggerService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import LoggerService from "../../domain/service/loggerService";

export default class ConsoleLoggerService implements LoggerService {
log(...args: unknown[]) {
console.log(...args);
}
}
Loading

0 comments on commit b85ddb9

Please sign in to comment.