Skip to content

Commit

Permalink
refactor: message and command entities (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrunton authored Sep 22, 2024
1 parent 99c54f9 commit f5898f5
Show file tree
Hide file tree
Showing 28 changed files with 399 additions and 434 deletions.
9 changes: 5 additions & 4 deletions services/api/src/app/messages/command.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Command } from '@entities/command';
import { User } from '@entities/users/user';
import { Injectable } from '@nestjs/common';
import { HelpCommandUseCase } from '@usecases/commands/help';
import { RenameRoomUseCase } from '@usecases/rooms/rename';
import { RenameUserUseCase } from '@usecases/users/rename';
import { Dispatcher } from '@entities/messages/message';
import { LoremCommandUseCase } from '@usecases/commands/lorem';
import { ParseCommandUseCase } from '@usecases/commands/parse';
import { ParseCommandUseCase } from '@usecases/commands/parse/parse-command';
import { ConfigureRoomUseCase } from '@usecases/rooms/configure-room';
import { P, match } from 'ts-pattern';
import { InviteUseCase } from '@usecases/memberships/invite';
import { LeaveRoomUseCase } from '@usecases/memberships/leave';
import { AboutRoomUseCase } from '@usecases/rooms/about-room';
import { ApproveRequestUseCase } from '@usecases/memberships/approve-request';
import { IncomingCommand } from '@entities/commands';

@Injectable()
export class CommandService {
Expand All @@ -30,9 +30,10 @@ export class CommandService {
readonly dispatcher: Dispatcher,
) {}

async exec(command: Command, authenticatedUser: User): Promise<void> {
async exec(command: IncomingCommand, authenticatedUser: User): Promise<void> {
const { roomId } = command;
const parsedCommand = await this.parse.exec(command);
const parsedCommand = this.parse.exec(command);

return match(parsedCommand)
.with({ tag: 'help' }, () =>
this.help.exec({ roomId, authenticatedUser }),
Expand Down
2 changes: 1 addition & 1 deletion services/api/src/app/messages/messages.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MessagesController } from './messages.controller';
import { AuthModule } from '@app/auth/auth.module';
import { SendMessageUseCase } from '@usecases/messages/send';
import { GetMessagesUseCase } from '@usecases/messages/get-messages';
import { ParseCommandUseCase } from '@usecases/commands/parse';
import { ParseCommandUseCase } from '@usecases/commands/parse/parse-command';
import { CommandService } from '@app/messages/command.service';
import { RenameRoomUseCase } from '@usecases/rooms/rename';
import { RenameUserUseCase } from '@usecases/users/rename';
Expand Down
8 changes: 4 additions & 4 deletions services/api/src/app/messages/messages.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { User } from '@entities/users/user';
import { mock, MockProxy } from 'jest-mock-extended';
import { SendMessageUseCase } from '@usecases/messages/send';
import { CommandService } from './command.service';
import { Command } from '@entities/command';
import { UnauthorizedException } from '@nestjs/common';
import { systemUser } from '@entities/users/system-user';
import { IncomingCommand } from '@entities/commands';

describe('MessagesService', () => {
let service: MessagesService;
Expand Down Expand Up @@ -60,10 +60,10 @@ describe('MessagesService', () => {

await service.handleMessage(message, authenticatedUser);

const expectedCommand: Command = {
tokens: ['help'],
canonicalInput: '/help',
const expectedCommand: IncomingCommand = {
content: '/help',
roomId,
authorId: authenticatedUser.id,
};
expect(command.exec).toHaveBeenCalledWith(
expectedCommand,
Expand Down
29 changes: 17 additions & 12 deletions services/api/src/app/messages/messages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { HttpException, Injectable } from '@nestjs/common';
import { CreateMessageDto } from './dto/messages';
import { SendMessageUseCase } from '@usecases/messages/send';
import { CommandService } from '@app/messages/command.service';
import { isCommand, parseMessage } from '@usecases/messages/parse-message';
import { systemUser } from '@entities/users/system-user';
import { User } from '@entities/users/user';
import { IncomingMessage } from '@entities/messages/message';
import { isCommand } from '@entities/commands';

@Injectable()
export class MessagesService {
Expand All @@ -17,10 +18,10 @@ export class MessagesService {
incoming: CreateMessageDto,
authenticatedUser: User,
): Promise<void> {
const message = parseMessage({
const message: IncomingMessage = {
...incoming,
authorId: authenticatedUser.id,
});
};

try {
if (isCommand(message)) {
Expand All @@ -30,18 +31,22 @@ export class MessagesService {
}
} catch (e) {
if (e instanceof HttpException) {
await this.send.exec(
{
content: e.message,
roomId: incoming.roomId,
recipientId: authenticatedUser.id,
authorId: 'system',
},
return await this.send.exec(
this.getErrorMessage(e, message),
systemUser,
);
} else {
throw e;
}

throw e;
}
}

private getErrorMessage(e: HttpException, message: IncomingMessage) {
return {
content: e.message,
roomId: message.roomId,
recipientId: message.authorId,
authorId: 'system',
};
}
}
22 changes: 0 additions & 22 deletions services/api/src/domain/entities/command.ts

This file was deleted.

14 changes: 14 additions & 0 deletions services/api/src/domain/entities/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IncomingMessage } from '@entities/messages/message';

/**
* An incoming message which is a command. Any message with content prefixed by a forward slash is
* considered a command, where or not it can be parsed. If it cannot be parsed, it is an invalid
* command.
*/
export type IncomingCommand = IncomingMessage & {
content: `/${string}`;
};

export const isCommand = (
message: IncomingMessage,
): message is IncomingCommand => message.content.startsWith('/');
39 changes: 22 additions & 17 deletions services/api/src/domain/entities/messages/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,9 @@ export enum UpdatedEntity {
}

/**
* A message that has been sent by a user and is stored in the system.
* A message received by the system but not yet stored, executed or dispatched to clients.
*/
export type SentMessage = {
/**
* Unique identifier for the message.
*/
id: string;

/**
* The time the message was sent (in ms since the epoch).
*/
time: number;

export type IncomingMessage = {
/**
* The text content of the message.
*/
Expand All @@ -34,7 +24,12 @@ export type SentMessage = {
* The room the message was sent to.
*/
roomId: string;
};

/**
* Type for draft messages which have not yet been sent by the system.
*/
export type DraftMessage = IncomingMessage & {
/**
* The recipient of the message.
* If undefined, this is a public message sent to the room.
Expand All @@ -50,6 +45,21 @@ export type SentMessage = {
updatedEntities?: UpdatedEntity[];
};

/**
* A message that has been sent by a user and is stored in the system.
*/
export type SentMessage = DraftMessage & {
/**
* Unique identifier for the message.
*/
id: string;

/**
* The time the message was sent (in ms since the epoch).
*/
time: number;
};

/**
* A refinement of the `Message` type to indicate it is private.
*/
Expand All @@ -66,11 +76,6 @@ export const isPrivate = (message: SentMessage): message is PrivateMessage => {
return (message as PrivateMessage).recipientId !== undefined;
};

/**
* Type for draft messages which have not yet been sent by the system.
*/
export type DraftMessage = Omit<SentMessage, 'id' | 'time'>;

/**
* Abstract class for dispatching and subscribing to messages. All messages will be sent and
* delivered to subscribers through a `Dispatcher`.
Expand Down
9 changes: 5 additions & 4 deletions services/api/src/domain/usecases/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Dispatcher } from '@entities/messages/message';
import { User } from '@entities/users/user';
import { Injectable } from '@nestjs/common';
import { parsers } from './parse/parsers';

import { commands } from '@usecases/commands/parse/commands';

export type HelpParams = {
authenticatedUser: User;
Expand All @@ -24,9 +25,9 @@ export class HelpCommandUseCase {

private generateContent() {
const title = 'Type to chat, or enter one of the following commands:';
const commands = parsers.map(
(parser) => `* \`${parser.signature}\`: ${parser.summary}`,
const commandSummaries = commands.map(
(command) => `* \`${command.signature}\`: ${command.summary}`,
);
return [title, ...commands].join('\n');
return [title, ...commandSummaries].join('\n');
}
}
Loading

0 comments on commit f5898f5

Please sign in to comment.