Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.env
.env.example
dist
coverage
15 changes: 15 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:22-alpine

WORKDIR /backend

COPY package*.json ./

RUN npm install --quiet --no-optional --no-fund --loglevel=error

COPY . .

EXPOSE ${PORT:-3000}

RUN npm run build

CMD ["npm", "run", "start:prod"]
20 changes: 20 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"start:prod": "node dist/src/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest --testPathPatterns \"^(?!.*\\.int\\.spec\\.ts).*\\.spec\\.ts$\"",
"test:watch": "jest --watch",
Expand All @@ -24,6 +24,7 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.0.1",
"@nestjs/event-emitter": "^3.1.0",
"@nestjs/jwt": "^11.0.2",
"@nestjs/mongoose": "^11.0.4",
"@nestjs/passport": "^11.0.5",
Expand Down
8 changes: 8 additions & 0 deletions backend/shared/events/access-request.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

export class AccessRequestEvent {
constructor(
public readonly name: string,
public readonly email: string,
public readonly cnpj: string,
) {}
}
13 changes: 9 additions & 4 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import { FileModule } from './modules/file/file.module';
import { FaqModule } from './modules/faq/faq.module';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { NotificationModule } from './modules/notification/notification.module';
import { EventEmitterModule } from '@nestjs/event-emitter';


@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/uploads',
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/uploads',
}),
ConfigModule.forRoot({
isGlobal: true,
Expand All @@ -37,9 +40,11 @@ import { join } from 'path';
TicketModule,
EmailModule,
FileModule,
FaqModule
FaqModule,
NotificationModule,
EventEmitterModule.forRoot(),
],
controllers: [],
providers: [],
})
export class AppModule {}
export class AppModule {}
31 changes: 29 additions & 2 deletions backend/src/modules/Messages/presentation/message.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { MessageRepositoryMongodb } from '../infra/message.repository.mongodb';
import { ReceivedMessageNotificationEvent } from '../../../shared/events/received-message-notification.event';
import { NotificationType } from '../../notification/shared/enums/notification.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';

@WebSocketGateway({ cors: true })
export class MessageGateway
Expand All @@ -19,7 +22,9 @@ export class MessageGateway
server: Server;

// Injeta o repositório
constructor(private readonly messageRepository: MessageRepositoryMongodb) {}
constructor(private readonly messageRepository: MessageRepositoryMongodb,
private readonly eventEmitter:EventEmitter2,
) {}

// Método disparado quando um usuário conecta
handleConnection(client: Socket) {
Expand Down Expand Up @@ -70,7 +75,29 @@ export class MessageGateway
fileIds: data.fileIds || [],
});

console.log(`Mensagem salva no banco com ID: ${mensagemSalva.id}`);
this.eventEmitter.emit(
NotificationType.NEW_MESSAGE,

new ReceivedMessageNotificationEvent(

data.receiverId,

mensagemSalva.chatId,

mensagemSalva.id,

mensagemSalva.senderId,

data.senderName,

mensagemSalva.content,

mensagemSalva.type || 'TEXT',

new Date(),
),
);
console.log(`Mensagem salva no banco com ID: ${mensagemSalva.id}`);

// Emite a mensagem para os clientes dentro da sala do chamado
this.server.to(data.chatId).emit('novaMensagem', mensagemSalva);
Expand Down
87 changes: 77 additions & 10 deletions backend/src/modules/chat/application/chat.file-message.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { EventEmitter2 } from '@nestjs/event-emitter';

import { ChatService } from './chat.service';
import { TicketSchemaClass } from '../../ticket/infra/schemas/ticket.mongo.schema';
import { User } from '../../user/user.schema';

import { NotFoundException } from '@nestjs/common';

import type { IChatRepository } from '../domain/chat.repository';
import type { IMessageRepository } from '../../Messages/domain/message.repository';

import { UserRole } from '../../shared/enums/user.enum';

const mockChatRepository: jest.Mocked<IChatRepository> = {
Expand All @@ -22,6 +27,14 @@ const mockMessageRepository: jest.Mocked<IMessageRepository> = {
findByChatId: jest.fn(),
};

const mockEventEmitter = {
emit: jest.fn(),
};

const mockUserModel = {
findById: jest.fn(),
};

const CHAT_ID = 'chat-001';
const CLIENT_ID = 'user-001';

Expand All @@ -35,26 +48,62 @@ const mockChat = {
createdAt: new Date(),
};

const mockTicketQuery = {
exec: jest.fn().mockResolvedValue(null),
};

const mockTicketModel = {
findById: jest.fn().mockReturnValue(mockTicketQuery),
};

describe('ChatService — envio de mensagem com arquivo', () => {
let service: ChatService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ChatService,
{ provide: 'IChatRepository', useValue: mockChatRepository },
{ provide: 'IMessageRepository', useValue: mockMessageRepository },
{ provide: getModelToken(TicketSchemaClass.name), useValue: {} },
{ provide: getModelToken(UserRole.ADMIN ? 'User' : 'User'), useValue: {} },

{
provide: 'IChatRepository',
useValue: mockChatRepository,
},

{
provide: 'IMessageRepository',
useValue: mockMessageRepository,
},

{
provide: getModelToken(TicketSchemaClass.name),
useValue: {},
},

{
provide: getModelToken(User.name),
useValue: mockUserModel,
},

{
provide: EventEmitter2,
useValue: mockEventEmitter,
},
],
}).compile();

service = module.get<ChatService>(ChatService);

jest.clearAllMocks();

mockUserModel.findById.mockResolvedValue({
_id: CLIENT_ID,
name: 'Gabriel',
});
});

it('should send message with attached file', async () => {
const FILE_ID = 'file-001';

const mockSavedMessage = {
id: 'msg-001',
chatId: CHAT_ID,
Expand All @@ -66,7 +115,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
};

mockChatRepository.findById.mockResolvedValue(mockChat as any);
mockMessageRepository.create.mockResolvedValue(mockSavedMessage as any);

mockMessageRepository.create.mockResolvedValue(
mockSavedMessage as any,
);

const result = await service.sendMessage(
CHAT_ID,
Expand All @@ -77,7 +129,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
);

expect(result).toEqual(mockSavedMessage);
expect(mockMessageRepository.create).toHaveBeenCalledWith({

expect(
mockMessageRepository.create,
).toHaveBeenCalledWith({
chatId: CHAT_ID,
senderId: CLIENT_ID,
content: 'Arquivo anexado',
Expand All @@ -86,6 +141,8 @@ describe('ChatService — envio de mensagem com arquivo', () => {
attachmentUrl: undefined,
type: 'TEXT',
});

expect(mockEventEmitter.emit).toHaveBeenCalled();
});

it('should send message without fileIds', async () => {
Expand All @@ -99,8 +156,13 @@ describe('ChatService — envio de mensagem com arquivo', () => {
createdAt: new Date(),
};

mockChatRepository.findById.mockResolvedValue(mockChat as any);
mockMessageRepository.create.mockResolvedValue(mockSavedMessage as any);
mockChatRepository.findById.mockResolvedValue(
mockChat as any,
);

mockMessageRepository.create.mockResolvedValue(
mockSavedMessage as any,
);

const result = await service.sendMessage(
CHAT_ID,
Expand All @@ -110,7 +172,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
);

expect(result).toEqual(mockSavedMessage);
expect(mockMessageRepository.create).toHaveBeenCalledWith({

expect(
mockMessageRepository.create,
).toHaveBeenCalledWith({
chatId: CHAT_ID,
senderId: CLIENT_ID,
content: 'Mensagem normal',
Expand All @@ -119,6 +184,8 @@ describe('ChatService — envio de mensagem com arquivo', () => {
attachmentUrl: undefined,
type: 'TEXT',
});

expect(mockEventEmitter.emit).toHaveBeenCalled();
});

it('should throw NotFoundException when chat does not exist', async () => {
Expand All @@ -131,7 +198,7 @@ describe('ChatService — envio de mensagem com arquivo', () => {
UserRole.CLIENT,
'Teste',
['file-001'],
)
),
).rejects.toThrow(NotFoundException);
});
});
Loading
Loading