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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ temp/
# ======================
# Files local storage
# ======================
uploads/
uploads/

# ======================
# AI fast generated models
# ======================
backend/model.nlp
2 changes: 1 addition & 1 deletion backend/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../user/dtos/createUserDTO';
import { UserDetails } from '../user/user.interface';
import { ExistingUserDTO } from '../user/dtos/existingUserDTO';
import { UserRole } from '../user/user.schema';
import { UserRole } from '../shared/enums/user.enum';
import { Roles } from './guards/roles.decorator';
import { JwtGuard } from './guards/jwt.guard';
import { RolesGuard } from './guards/roles.guard';
Expand Down
3 changes: 2 additions & 1 deletion backend/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CreateUserDTO } from '../user/dtos/createUserDTO';
import { UserDetails } from '../user/user.interface';
import { ExistingUserDTO } from '../user/dtos/existingUserDTO';
import { JwtService } from '@nestjs/jwt';
import { UserRole } from '../user/user.schema';
import { UserRole } from '../shared/enums/user.enum';
import { EmailService } from '../email/email.service';

@Injectable()
Expand Down Expand Up @@ -85,6 +85,7 @@ export class AuthService {
sub: user.id,
email: user.email,
role: user.role,
categories: user.categories ?? undefined,
});
return { token: jwt };
}
Expand Down
1 change: 1 addition & 0 deletions backend/src/modules/auth/guards/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
id: payload.sub,
email: payload.email,
role: payload.role,
categories: (payload.categories ?? []).map((c) => c.id),
};
}
}
2 changes: 1 addition & 1 deletion backend/src/modules/auth/guards/roles.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../../user/user.schema';
import { UserRole } from '../../shared/enums/user.enum';

export const ROLES_KEY = 'roles';

Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/auth/guards/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {

import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
import { UserRole } from '../../user/user.schema';
import { UserRole } from '../../shared/enums/user.enum';

@Injectable()
export class RolesGuard implements CanActivate {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/category/category.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { CategoryDetails } from './category.interface';
import { Roles } from '../auth/guards/roles.decorator';
import { JwtGuard } from '../auth/guards/jwt.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { UserRole } from '../user/user.schema';
import {
ApiBearerAuth,
ApiBody,
Expand All @@ -24,6 +23,7 @@ import {
} from '@nestjs/swagger';
import { CreateCategoryDTO } from './dtos/createCategoryDTO';
import { UpdateCategoryDTO } from './dtos/updateCategoryDTO';
import { UserRole } from '../shared/enums/user.enum';

@ApiTags('Category')
@ApiBearerAuth()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { ChatService } from './chat.service';
import { TicketSchemaClass } from '../../ticket/infra/schemas/ticket.mongo.schema';
import { UserRole } 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> = {
create: jest.fn(),
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/chat/application/chat.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ForbiddenException, NotFoundException } from '@nestjs/common';
import { ChatDetails, ChatStatus } from '../domain/chat.entity';
import { IChatRepository } from '../domain/chat.repository';
import { IMessageRepository } from '../../Messages/domain/message.repository';
import { UserRole } from '../../user/user.schema';
import { UserRole } from '../../shared/enums/user.enum';

const mockChatRepository: jest.Mocked<IChatRepository> = {
create: jest.fn(),
Expand Down
4 changes: 3 additions & 1 deletion backend/src/modules/chat/application/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import type { IChatRepository } from '../domain/chat.repository';
import type { IMessageRepository } from '../../Messages/domain/message.repository';
import type { ChatDetails } from '../domain/chat.entity';
import { ChatStatus } from '../domain/chat.entity';
import { User, UserDocument, UserRole } from '../../user/user.schema';
import { UserRole } from '../../shared/enums/user.enum';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { TicketSchemaClass, TicketDocument } from '../../ticket/infra/schemas/ticket.mongo.schema';
import { from } from 'rxjs';
import { User, UserDocument } from '../../user/user.schema';

@Injectable()
export class ChatService {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/company/company.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { CompanyDetails } from './company.interface';
import { JwtGuard } from '../auth/guards/jwt.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/guards/roles.decorator';
import { UserRole } from '../user/user.schema';
import {
ApiBearerAuth,
ApiBody,
Expand All @@ -25,6 +24,7 @@ import {
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { UserRole } from '../shared/enums/user.enum';

@ApiTags('Company')
@ApiBearerAuth()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { ReadAllFaqUseCase } from "../../application/readAll/readAll.usecase";
import { ReadByIdFaqUseCase } from "../../application/readById/readById.usecase";
import { JwtGuard } from "../../../auth/guards/jwt.guard";
import { RolesGuard } from "../../../auth/guards/roles.guard";
import { UserRole } from "../../../user/user.schema";
import { CreateFaqRequest } from "../dtos/create.dto";
import { FaqMapper } from "../mappers/faq.mapper";
import { Roles } from "../../../auth/guards/roles.decorator";
import { UpdateFaqRequest } from "../dtos/update.dto";
import { UserRole } from "../../../shared/enums/user.enum";

@ApiTags('FAQ')
@ApiBearerAuth()
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/file/presentation/file.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { FileInterceptor } from '@nestjs/platform-express';
import { JwtGuard } from '../../auth/guards/jwt.guard';
import { RolesGuard } from '../../auth/guards/roles.guard';
import { Roles } from '../../auth/guards/roles.decorator';
import { UserRole } from '../../user/user.schema';
import { FileService } from '../application/file.service';
import { UploadFileDTO } from './dtos/uploadFileDTO';
import { multerConfig } from '../config/multer.config';
import type { Express, Request } from 'express';
import { UploadChatFileParamsDTO } from './dtos/uploadChatFileParamsDTO';
import { UserRole } from '../../shared/enums/user.enum';
import { profileMulterConfig } from '../config/profile-multer.config';
import type { Response } from 'express';

Expand Down
5 changes: 5 additions & 0 deletions backend/src/modules/shared/enums/user.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum UserRole {
CLIENT = 'client',
SUPPORT = 'support',
ADMIN = 'admin',
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { TriageService } from '../../../../triage/application/triage.service';
export interface CreateTicketInput {
title: string;
description: string;
clientId: string;
level?: number;
}

Expand All @@ -29,12 +28,16 @@ export class CreateTicketUseCase {
private readonly triageService: TriageService,
) {}

async execute(input: CreateTicketInput): Promise<CreateTicketOutput> {
async execute(
input: CreateTicketInput,
clientId: string,
): Promise<CreateTicketOutput> {
const triageResult = await this.triageService.classify(input.description);

const ticket = Ticket.create({
...input,
category: triageResult.category,
clientId: clientId,
});

const created = await this.repository.create(ticket);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/unbound-method */
import { describe, expect, it, beforeEach, jest } from '@jest/globals';
import { randomUUID } from 'crypto';
import { ITicketRepository } from '../../../domain/repository/ticket.repository.interface';
import { Ticket } from '../../../domain/entities/ticket.entity';
import { ReadAllTicketUseCase } from './readAll.usecase';
import { UserRole } from '../../../../shared/enums/user.enum';

describe('ReadAllTicketUseCase', () => {
let repository: jest.Mocked<ITicketRepository>;
Expand All @@ -27,7 +29,10 @@ describe('ReadAllTicketUseCase', () => {
it('should read all ticket successfully', async () => {
repository.readAll.mockResolvedValue([ticket]);

const output = await useCase.execute();
const output = await useCase.execute({
userId: randomUUID(),
role: UserRole.SUPPORT,
});

expect(output).toBeDefined();
expect(Array.isArray(output)).toBe(true);
Expand All @@ -39,4 +44,75 @@ describe('ReadAllTicketUseCase', () => {
expect(output).not.toHaveProperty('escalate');
expect(output).not.toHaveProperty('assignToAgent');
});

it('should read all tickets with clientId filter', async () => {
repository.readAll.mockResolvedValue([ticket]);
const output = await useCase.execute({
userId: ticket.clientId,
role: UserRole.CLIENT,
});
expect(output).toBeDefined();
expect(Array.isArray(output)).toBe(true);
expect(output[0].clientId).toBe(ticket.clientId);
expect(repository.readAll).toHaveBeenCalledWith({
clientId: ticket.clientId,
});

expect(output).not.toHaveProperty('toPrimitives');
expect(output).not.toHaveProperty('escalate');
expect(output).not.toHaveProperty('assignToAgent');
});

it('should read all tickets associated to the support agent or unassigned in their group', async () => {
const supId = randomUUID();
const categories = [randomUUID()];

const ticketAssignedToAgent = Ticket.create({
title: 'ticket atribuído ao agente',
category: categories[0],
description: 'descricao',
clientId: randomUUID(),
});
ticketAssignedToAgent.assignToAgent(supId);

const ticketUnassignedInGroup = Ticket.create({
title: 'ticket sem agente no grupo',
category: randomUUID(),
description: 'descricao',
clientId: randomUUID(),
});

const ticketOtherAgent = Ticket.create({
title: 'ticket de outro agente',
category: categories[0],
description: 'descricao',
clientId: randomUUID(),
});
ticketOtherAgent.assignToAgent(randomUUID());

repository.readAll.mockResolvedValue([
ticketAssignedToAgent,
ticketUnassignedInGroup,
]);

const output = await useCase.execute({
userId: supId,
categories: categories,
role: UserRole.SUPPORT,
});

expect(output).toBeDefined();
expect(Array.isArray(output)).toBe(true);
expect(output).toHaveLength(2);

expect(repository.readAll).toHaveBeenCalledWith({
agentId: supId,
categories: categories,
});

// Garante que retornou primitivos, não instâncias do domínio
expect(output[0]).not.toHaveProperty('toPrimitives');
expect(output[0]).not.toHaveProperty('escalate');
expect(output[0]).not.toHaveProperty('assignToAgent');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TicketStatus,
} from '../../../domain/entities/ticket.entity';
import { ITicketRepository } from '../../../domain/repository/ticket.repository.interface';
import { UserRole } from '../../../../shared/enums/user.enum';

export interface ReadAllTicketOutput {
id: string;
Expand All @@ -15,7 +16,6 @@ export interface ReadAllTicketOutput {
clientId: string;
status: TicketStatus;
agentId: string | null;
groupId: string | null;
escalationLevel: number;
createdAt: Date;
updatedAt: Date | null;
Expand All @@ -26,8 +26,19 @@ export interface ReadAllTicketOutput {
export class ReadAllTicketUseCase {
constructor(private readonly repository: ITicketRepository) {}

async execute(): Promise<ReadAllTicketOutput[]> {
const foundedTickets = await this.repository.readAll();
async execute(input: {
userId: string;
categories?: string[];
role: UserRole;
}): Promise<ReadAllTicketOutput[]> {
const filters =
input.role === UserRole.CLIENT
? { clientId: input.userId }
: input.role === UserRole.SUPPORT
? { agentId: input.userId, categories: input.categories }
: undefined;

const foundedTickets = await this.repository.readAll({ ...filters });

const convertedTickets = foundedTickets.map((t: Ticket) => {
const primitive = t.toPrimitives();
Expand All @@ -41,7 +52,6 @@ export class ReadAllTicketUseCase {
clientId: primitive.clientId,
status: primitive.status,
agentId: primitive.agentId,
groupId: primitive.groupId,
escalationLevel: primitive.escalationLevel,
createdAt: primitive.createdAt,
updatedAt: primitive.updatedAt,
Expand Down
Loading
Loading