Skip to content

Commit

Permalink
Merge pull request #69 from unb-mds/feature-#68/api-architecture
Browse files Browse the repository at this point in the history
Feature #68/api architecture
  • Loading branch information
DanielFsR authored Sep 4, 2024
2 parents 32b11ec + a9df3dd commit 9fb62cb
Show file tree
Hide file tree
Showing 31 changed files with 159 additions and 173 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response } from 'express';
import { MatriculasRepositoryPSQL } from '../database/psql/matriculasRepositoryPSQL';
import { MatriculasService } from '../services/matriculasServices';
import { EnrollmentService } from '../../application/services/enrollmentServices';
import { EnrollmentRepositoryPSQL } from '../../infrastructure/database/psql/enrollmentRepositoryPSQL';

export const EnrollmentController = async (req: Request, res: Response) => {
try {
Expand All @@ -13,7 +13,7 @@ export const EnrollmentController = async (req: Request, res: Response) => {
return res.status(400).json({ message: 'Municipio e Etapa são obrigatórios.' });
}

const service = new MatriculasService(new MatriculasRepositoryPSQL());
const service = new EnrollmentService(new EnrollmentRepositoryPSQL());
const result = await service.execute({ municipio, etapa });

res.json(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response } from 'express';
import { IndicadoresRepositoryPSQL } from '../database/psql/indicadoresRepositoryPSQL';
import { IndicadoresService } from '../services/indicadoresServices';
import { IndicatorService } from '../../application/services/indicatorServices';
import { IndicatorRepositoryPSQL } from '../../infrastructure/database/psql/indicatorRepositoryPSQL';

export const IndicatorController = async (req: Request, res: Response) => {
try {
Expand All @@ -21,7 +21,7 @@ export const IndicatorController = async (req: Request, res: Response) => {
.json({ message: 'Indicador, Etapa e Município são obrigatórios.' });
}

const service = new IndicadoresService(new IndicadoresRepositoryPSQL());
const service = new IndicatorService(new IndicatorRepositoryPSQL());
const result = await service.execute({ indicador, etapa, municipio });

res.json(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response } from 'express';
import { RankingRepositoryPSQL } from '../database/psql/rankingRepositoryPSQL';
import { RankingService } from '../services/rankingServices';
import { RankingRepositoryPSQL } from '../../infrastructure/database/psql/rankingRepositoryPSQL';
import { RankingService } from '../../application/services/rankingServices';

export const RankingController = async (req: Request, res: Response) => {
try {
Expand Down
14 changes: 14 additions & 0 deletions api/src/adapters/repositories/enrollmentRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { EnrollmentInput } from '../../application/services/enrollmentServices';

export type EnrollmentRepositoryOutput = {
ano: number;
raca: string;
rede: string;
etapa: string;
matricula: number;
municipio: string;
};

export abstract class EnrollmentRepository {
abstract fetch(input: EnrollmentInput): Promise<EnrollmentRepositoryOutput[]>;
}
15 changes: 15 additions & 0 deletions api/src/adapters/repositories/indicatorRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IndicatorInput } from '../../application/services/indicatorServices';

export type IndicatorRepositoryOutput = {
ano: number;
rede: string;
etapa: string;
taxa_de_aprovacao: number;
taxa_de_reprovacao: number;
taxa_de_abandono: number;
municipio: string;
};

export abstract class IndicatorRepository {
abstract fetch(input: IndicatorInput): Promise<IndicatorRepositoryOutput[]>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { RankingInput } from '../services/rankingServices';
import { RankingInput } from '../../application/services/rankingServices';

// output vindo do banco de dados
export type RankingRepositoryOutput = {
ano: number;
raca: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
import { describe, expect, test } from 'vitest';
import { genericData } from '../database/memory/data/matriculasMemoryData';
import { MatriculasRepositoryMemory } from '../database/memory/matriculasRepositoryMemory';
import { MatriculasInput, MatriculasService } from './matriculasServices';
import { genericData } from '../../infrastructure/database/memory/data/enrollmentMemoryData';
import { EnrollmentRepositoryMemory } from '../../infrastructure/database/memory/enrollmentRepositoryMemory';
import { EnrollmentInput, EnrollmentService } from './enrollmentServices';

describe('Matriculas Service', () => {
const matriculaRepository = new MatriculasRepositoryMemory();
const matriculasService = new MatriculasService(matriculaRepository);
describe('Enrollment Service', () => {
const enrollmentRepository = new EnrollmentRepositoryMemory();
const enrollmentService = new EnrollmentService(enrollmentRepository);

matriculaRepository.DATA_IN_MEMORY = genericData.map((item) => ({
enrollmentRepository.DATA_IN_MEMORY = genericData.map((item) => ({
...item,
municipio: item.municipio.toString(),
}));

const input: MatriculasInput = {
const input: EnrollmentInput = {
etapa: 'EM',
municipio: '3106200',
};

test('should return categories length with success', async () => {
const output = await matriculasService.execute(input);
const output = await enrollmentService.execute(input);
expect(output.categories).toHaveLength(8);
});

test('should return first categorie with success', async () => {
const output = await matriculasService.execute(input);
const output = await enrollmentService.execute(input);
expect(output.categories[0]).toEqual('2020 Pública');
});

test('should return second categorie with success', async () => {
const output = await matriculasService.execute(input);
const output = await enrollmentService.execute(input);
expect(output.categories[1]).toEqual('2020 Privada');
});

test('should return series length with success', async () => {
const output = await matriculasService.execute(input);
const output = await enrollmentService.execute(input);
expect(output.series).toHaveLength(2);
});

test('should return an Error when not exists register with etapa filter', async () => {
const incorrectEtapaInput = { ...input, etapa: 'Ensino' };

await expect(matriculasService.execute(incorrectEtapaInput)).rejects.toThrowError(
await expect(enrollmentService.execute(incorrectEtapaInput)).rejects.toThrowError(
'Nenhum dado foi encontrado para estes filtros.',
);
});

test('should return an Error when not exists register with municipio filter', async () => {
const incorrectMunicipioInput = { ...input, municipio: 'BH' };

await expect(matriculasService.execute(incorrectMunicipioInput)).rejects.toThrowError(
await expect(enrollmentService.execute(incorrectMunicipioInput)).rejects.toThrowError(
'Nenhum dado foi encontrado para estes filtros.',
);
});
test('should return data in order', async () => {
matriculaRepository.DATA_IN_MEMORY = genericData.map((item) => ({
enrollmentRepository.DATA_IN_MEMORY = genericData.map((item) => ({
...item,
municipio: item.municipio.toString(),
}));
const response = await matriculasService.execute(input);
const response = await enrollmentService.execute(input);
expect(response.series[0].name).toEqual('Pretos/Pardos');
expect(response.series[0].data).toEqual([0, 0, 0, 0, 85, 21, 100, 28]);
expect(response.series[1].name).toEqual('Brancos');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MatriculasRepository } from '../repositories/matriculasRepository';
import { EnrollmentRepository } from '../../adapters/repositories/enrollmentRepository';

export type MatriculasInput = {
export type EnrollmentInput = {
municipio: string;
etapa: string;
};
Expand All @@ -13,11 +13,11 @@ type Output = {
categories: string[];
};

export class MatriculasService {
constructor(private matriculasRepository: MatriculasRepository) {}
export class EnrollmentService {
constructor(private enrollmentRepository: EnrollmentRepository) {}

async execute(input: MatriculasInput): Promise<Output> {
const data = await this.matriculasRepository.fetch(input);
async execute(input: EnrollmentInput): Promise<Output> {
const data = await this.enrollmentRepository.fetch(input);

if (!data || data.length === 0) {
throw new Error('Nenhum dado foi encontrado para estes filtros.');
Expand Down Expand Up @@ -59,15 +59,6 @@ export class MatriculasService {
}
});

// REMOVE CATEGORIAS VAZIAS
// for (let i = categories.length - 1; i >= 0; i--) {
// if (newData['Pretos/Pardos'][i] <= 0 && newData.Brancos[i] <= 0) {
// newData['Pretos/Pardos'].splice(i, 1);
// newData.Brancos.splice(i, 1);
// categories.splice(i, 1);
// }
// }

const series = Object.entries(newData).map(([name, data]) => ({
name,
data,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,66 @@
import { describe, expect, test } from 'vitest';
import { genericData } from '../database/memory/data/indicadoresMemoryData';
import { IndicadoresRepositoryMemory } from '../database/memory/indicadoresRepositoryMemory';
import { IndicadoresInput, IndicadoresService } from './indicadoresServices';
import { genericData } from '../../infrastructure/database/memory/data/indicatorMemoryData';
import { IndicatorRepositoryMemory } from '../../infrastructure/database/memory/indicatorRepositoryMemory';
import { IndicatorInput, IndicatorService } from './indicatorServices';

describe('Indicadores Service', () => {
const indicadoresRepository = new IndicadoresRepositoryMemory();
const indicadoresService = new IndicadoresService(indicadoresRepository);
describe('Indicator Service', () => {
const indicatorRepository = new IndicatorRepositoryMemory();
const indicatorService = new IndicatorService(indicatorRepository);

indicadoresRepository.DATA_IN_MEMORY = genericData.map((item) => ({
indicatorRepository.DATA_IN_MEMORY = genericData.map((item) => ({
...item,
municipio: item.municipio.toString(),
}));

const input: IndicadoresInput = {
const input: IndicatorInput = {
indicador: 'taxa_de_aprovacao',
etapa: 'EF2',
municipio: '3101102',
};

test('should return categories length with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.categories).toHaveLength(4);
});

test('should return first category with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.categories[0]).toEqual(2020);
});

test('should return last categorie with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.categories[3]).toEqual(2023);
});

test('should return series length with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.series).toHaveLength(2);
});

test('should return first series with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.series[0].name).toEqual('Pública');
expect(output.series[0].data).toHaveLength(4);
expect(output.series[0].data[0]).toEqual(97.9);
expect(output.series[0].data[1]).toEqual(97.3);
});

test('should return second series with success', async () => {
const output = await indicadoresService.execute(input);
const output = await indicatorService.execute(input);
expect(output.series[1].name).toEqual('Privada');
expect(output.series[1].data).toHaveLength(4);
expect(output.series[1].data[0]).toEqual(100);
expect(output.series[1].data[1]).toEqual(99);
});

test('should return an Error when not exists data with municipio filter', async () => {
const incorrectIndicadorInput = { ...input, municipio: 'Onde Judas perdeu as botas' };
const incorrectIndicadorInput = {
...input,
municipio: 'Onde Judas perdeu as botas',
};

await expect(indicadoresService.execute(incorrectIndicadorInput)).rejects.toThrow(
await expect(indicatorService.execute(incorrectIndicadorInput)).rejects.toThrow(
'Nenhum dado foi encontrado para estes filtros.',
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IndicadoresRepository } from '../repositories/indicadoresRepository';
import { IndicatorRepository } from '../../adapters/repositories/indicatorRepository';

export type IndicadoresInput = {
export type IndicatorInput = {
indicador: string;
etapa: string;
municipio: string;
Expand All @@ -14,11 +14,11 @@ type Output = {
}[];
};

export class IndicadoresService {
constructor(private indicadoresRepository: IndicadoresRepository) {}
export class IndicatorService {
constructor(private indicatorRepository: IndicatorRepository) {}

async execute(input: IndicadoresInput): Promise<Output> {
const data = await this.indicadoresRepository.fetch(input);
async execute(input: IndicatorInput): Promise<Output> {
const data = await this.indicatorRepository.fetch(input);

if (!data || data.length === 0) {
throw new Error('Nenhum dado foi encontrado para estes filtros.');
Expand All @@ -30,17 +30,14 @@ export class IndicadoresService {
};
const categories: number[] = [];

// Definindo os anos esperados
const anos = [2020, 2021, 2022, 2023];

// Criando combinações de ano
anos.forEach((ano) => {
categories.push(ano);
newData.Pública.push(0);
newData.Privada.push(0);
});

// Atualizando dados de indicadores
data.forEach((entry: { [key: string]: any }) => {
const index = categories.indexOf(entry.ano);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, test } from 'vitest';
import { rankingData } from '../database/memory/data/rankingMemoryData';
import { RankingRepositoryMemory } from '../database/memory/rankingRepositoryMemory';
import { genericData } from '../../infrastructure/database/memory/data/rankingMemoryData';
import { RankingRepositoryMemory } from '../../infrastructure/database/memory/rankingRepositoryMemory';
import { RankingInput, RankingService } from './rankingServices';

describe('Ranking Service', () => {
Expand All @@ -10,7 +10,7 @@ describe('Ranking Service', () => {
beforeEach(() => {
rankingRepository = new RankingRepositoryMemory();
rankingService = new RankingService(rankingRepository);
rankingRepository.DATA_IN_MEMORY = rankingData; // Reset data before each test
rankingRepository.DATA_IN_MEMORY = genericData;
});

const input: RankingInput = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { RankingRepository } from '../repositories/rankingRepository';
import { RankingRepository } from '../../adapters/repositories/rankingRepository';

export type RankingInput = {
ano: number;
etapa: string;
};

type Output = {
name: string; // Nome do município
value: number; // Módulo da diferença percentual entre rede pública e privada
name: string;
value: number;
}[];

export class RankingService {
Expand Down Expand Up @@ -66,7 +66,6 @@ export class RankingService {
const totalPrivada =
newData[municipio].pretoPrivada + newData[municipio].brancoPrivada;

// Exclui o município se qualquer um dos valores totais for inferior a 10
if (totalPublica < 10 || totalPrivada < 10) {
delete newData[municipio];
return null;
Expand All @@ -85,7 +84,7 @@ export class RankingService {
value: parseFloat(diferencaPorcentagem.toFixed(2)),
};
})
.filter((item) => item !== null); // Filtra os municípios que foram excluídos
.filter((item) => item !== null);

return response;
}
Expand Down
Loading

0 comments on commit 9fb62cb

Please sign in to comment.