-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #63 from ufabc-next/migrate/extension-routes
Movendo rotas
- Loading branch information
Showing
69 changed files
with
1,772 additions
and
1,102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { currentQuad } from '@next/common'; | ||
import { StudentModel } from '@/models/Student.js'; | ||
import type { onRequestAsyncHookHandler } from 'fastify'; | ||
|
||
export const setStudentId: onRequestAsyncHookHandler = async ( | ||
request, | ||
reply, | ||
) => { | ||
const { aluno_id } = request.query as { aluno_id: number }; | ||
const season = currentQuad(); | ||
|
||
const student = await StudentModel.findOne({ | ||
season, | ||
aluno_id, | ||
}); | ||
|
||
// if not found block route | ||
if (!student) { | ||
return reply.forbidden('Student_id'); | ||
} | ||
|
||
// if not, pass | ||
return; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
apps/core/src/modules/Entities/disciplinas/disciplina.handlers.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import { sortBy as LodashSortBy } from 'lodash-es'; | ||
import { courseId, currentQuad } from '@next/common'; | ||
import { StudentModel } from '@/models/Student.js'; | ||
import type { Disciplina } from '@/models/Disciplina.js'; | ||
import type { DisciplinaService } from './disciplina.service.js'; | ||
import type { FastifyReply, FastifyRequest } from 'fastify'; | ||
|
||
export type DisciplinaKicksRequest = { | ||
Params: { | ||
disciplinaId: number; | ||
}; | ||
Querystring: { | ||
aluno_id: number; | ||
sort?: string; | ||
}; | ||
}; | ||
|
||
export class DisciplinaHandler { | ||
constructor(private readonly disciplinaService: DisciplinaService) {} | ||
|
||
async listDisciplinas(request: FastifyRequest) { | ||
const season = currentQuad(); | ||
const { redis } = request.server; | ||
const cacheKey = `all_disciplinas_${season}`; | ||
const cachedResponse = await redis.get(cacheKey); | ||
|
||
if (cachedResponse) { | ||
return cachedResponse; | ||
} | ||
|
||
const disciplinas = await this.disciplinaService.findDisciplinas(season); | ||
|
||
await redis.set(cacheKey, JSON.stringify(disciplinas), 'EX', 60 * 60 * 24); | ||
return disciplinas; | ||
} | ||
|
||
async listDisciplinasKicks( | ||
request: FastifyRequest<DisciplinaKicksRequest>, | ||
reply: FastifyReply, | ||
) { | ||
const { disciplinaId } = request.params; | ||
const { sort } = request.query; | ||
|
||
if (!disciplinaId) { | ||
await reply.badRequest('Missing DisciplinaId'); | ||
} | ||
|
||
const season = currentQuad(); | ||
const disciplina = await this.disciplinaService.findDisciplina( | ||
season, | ||
disciplinaId, | ||
); | ||
|
||
if (!disciplina) { | ||
return reply.notFound('Disciplina not found'); | ||
} | ||
|
||
// create sort mechanism | ||
const kicks = sort || kickRule(disciplina); | ||
// @ts-expect-error for now | ||
const order = [kicks.length || 0].fill('desc'); | ||
|
||
// turno must have a special treatment | ||
const turnoIndex = kicks.indexOf('turno'); | ||
if (turnoIndex !== -1) { | ||
// @ts-expect-error for now | ||
order[turnoIndex] = disciplina.turno === 'diurno' ? 'asc' : 'desc'; | ||
} | ||
|
||
const isAfterKick = [disciplina.after_kick].filter(Boolean).length; | ||
const resolveKicked = resolveMatricula(disciplina, isAfterKick); | ||
|
||
const kickedsMap = new Map( | ||
resolveKicked.map((kicked) => [kicked.aluno_id, kicked]), | ||
); | ||
const students = await this.disciplinaService.findStudentCourses( | ||
season, | ||
resolveKicked.map((kick) => kick.aluno_id), | ||
); | ||
|
||
const interIds = [ | ||
await courseId( | ||
'Bacharelado em Ciência e Tecnologia', | ||
season, | ||
// TODO(Joabe): refac later | ||
StudentModel, | ||
), | ||
await courseId( | ||
'Bacharelado em Ciências e Humanidades', | ||
season, | ||
// TODO(Joabe): refac later | ||
StudentModel, | ||
), | ||
]; | ||
|
||
const obrigatorias = getObrigatoriasFromDisciplinas( | ||
disciplina.obrigatorias, | ||
...interIds, | ||
); | ||
|
||
const studentsWithGraduationInfo = students.map((student) => { | ||
const reserva = obrigatorias.includes(student.cursos.id_curso); | ||
const graduationToStudent = Object.assign( | ||
{ | ||
aluno_id: student.aluno_id, | ||
cr: '-', | ||
cp: student.cursos.cp, | ||
ik: reserva ? student.cursos.ind_afinidade : 0, | ||
reserva, | ||
turno: student.cursos.turno, | ||
curso: student.cursos.nome_curso, | ||
}, | ||
kickedsMap.get(student.aluno_id) || {}, | ||
); | ||
|
||
return graduationToStudent; | ||
}); | ||
|
||
const sortedStudents = LodashSortBy(studentsWithGraduationInfo, kicks); | ||
|
||
const uniqueStudents = []; | ||
const uniqueStudentIds = new Set(); | ||
|
||
for (const student of sortedStudents) { | ||
if (!uniqueStudentIds.has(student.aluno_id)) { | ||
uniqueStudents.push(student); | ||
uniqueStudentIds.add(student.aluno_id); | ||
} | ||
} | ||
|
||
return uniqueStudents; | ||
} | ||
} | ||
|
||
function kickRule(disciplina: Disciplina) { | ||
const season = currentQuad(); | ||
let coeffRule = null; | ||
if ( | ||
season === '2020:2' || | ||
season === '2020:3' || | ||
season === '2021:1' || | ||
season === '2021:2' || | ||
season === '2021:3' || | ||
season === '2022:1' || | ||
season === '2022:2' || | ||
season === '2022:3' || | ||
season === '2023:1' || | ||
season === '2023:2' || | ||
season === '2023:3' || | ||
season === '2024:1' || | ||
season === '2024:2' || | ||
season === '2024:3' || | ||
season === '2025:1' | ||
) { | ||
coeffRule = ['cp', 'cr']; | ||
} else { | ||
coeffRule = disciplina.ideal_quad ? ['cr', 'cp'] : ['cp', 'cr']; | ||
} | ||
|
||
return ['reserva', 'turno', 'ik'].concat(coeffRule); | ||
} | ||
|
||
function resolveMatricula(disciplina: Disciplina, isAfterKick: number) { | ||
// if kick has not arrived, not one has been kicked | ||
if (!isAfterKick) { | ||
const registeredStudents = disciplina.alunos_matriculados || []; | ||
return registeredStudents.map((student) => ({ | ||
aluno_id: student, | ||
})); | ||
} | ||
|
||
// check diff between before_kick and after_kick | ||
const kicked = differenceOnKicks( | ||
disciplina.before_kick ?? [], | ||
disciplina.after_kick ?? [], | ||
); | ||
// return who has been kicked | ||
return disciplina.before_kick.map((student) => ({ | ||
aluno_id: student, | ||
kicked: kicked.includes(student), | ||
})); | ||
} | ||
|
||
function getObrigatoriasFromDisciplinas( | ||
obrigatorias: number[], | ||
...filterList: Awaited<ReturnType<typeof courseId>>[] | ||
) { | ||
const removeSet = new Set(filterList); | ||
return obrigatorias.filter((obrigatoria) => !removeSet.has(obrigatoria)); | ||
} | ||
|
||
const differenceOnKicks = (beforeKick: number[], afterKick: number[]) => | ||
beforeKick.filter((kick) => !afterKick.includes(kick)); |
57 changes: 57 additions & 0 deletions
57
apps/core/src/modules/Entities/disciplinas/disciplina.repository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type { Disciplina, DisciplinaModel } from '@/models/Disciplina.js'; | ||
import type { Student, StudentModel } from '@/models/Student.js'; | ||
import type { FilterQuery, PipelineStage, ProjectionType } from 'mongoose'; | ||
|
||
type StudentAggregate = Student & { | ||
cursos: Student['cursos'][number]; | ||
}; | ||
|
||
interface EntitiesDisciplinaRepository { | ||
findMany( | ||
filter: FilterQuery<Disciplina>, | ||
mapping?: ProjectionType<Disciplina>, | ||
populateFields?: string[], | ||
): Promise<Disciplina[] | null>; | ||
findOne(filter: FilterQuery<Disciplina>): Promise<Disciplina | null>; | ||
findCursos(filter: FilterQuery<Student>): Promise<StudentAggregate[]>; | ||
} | ||
|
||
export class DisciplinaRepository implements EntitiesDisciplinaRepository { | ||
constructor( | ||
private readonly disciplinaService: typeof DisciplinaModel, | ||
private readonly studentService: typeof StudentModel, | ||
) {} | ||
|
||
async findMany( | ||
filter: FilterQuery<Disciplina>, | ||
mapping?: ProjectionType<Disciplina>, | ||
populateFields?: string[], | ||
) { | ||
if (populateFields) { | ||
const disciplinas = await this.disciplinaService | ||
// eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument | ||
.find(filter, mapping) | ||
.populate(populateFields) | ||
.lean<Disciplina[]>({ virtuals: true }); | ||
return disciplinas; | ||
} | ||
const disciplinas = await this.disciplinaService | ||
// eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument | ||
.find(filter, mapping) | ||
.lean<Disciplina[]>({ virtuals: true }); | ||
return disciplinas; | ||
} | ||
|
||
async findOne(filter: FilterQuery<Disciplina>) { | ||
const disciplina = await this.disciplinaService.findOne(filter); | ||
|
||
return disciplina; | ||
} | ||
|
||
async findCursos(pipeline: PipelineStage[]) { | ||
const students = | ||
await this.studentService.aggregate<StudentAggregate>(pipeline); | ||
|
||
return students; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
apps/core/src/modules/Entities/disciplinas/disciplina.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { DisciplinaModel } from '@/models/Disciplina.js'; | ||
import { StudentModel } from '@/models/Student.js'; | ||
import { setStudentId } from '@/hooks/setStudentId.js'; | ||
import { DisciplinaRepository } from './disciplina.repository.js'; | ||
import { | ||
DisciplinaHandler, | ||
type DisciplinaKicksRequest, | ||
} from './disciplina.handlers.js'; | ||
import { DisciplinaService } from './disciplina.service.js'; | ||
|
||
import type { FastifyInstance } from 'fastify'; | ||
|
||
// eslint-disable-next-line require-await | ||
export async function disciplinasRoute(app: FastifyInstance) { | ||
const disciplinaRepository = new DisciplinaRepository( | ||
DisciplinaModel, | ||
StudentModel, | ||
); | ||
const disciplinaService = new DisciplinaService(disciplinaRepository); | ||
app.decorate('disciplinaService', disciplinaService); | ||
const disciplinaHandler = new DisciplinaHandler(disciplinaService); | ||
|
||
app.get('/disciplina', disciplinaHandler.listDisciplinas); | ||
app.get<DisciplinaKicksRequest>( | ||
'/disciplina/:disciplinaId/kicks', | ||
{ onRequest: [setStudentId] }, | ||
disciplinaHandler.listDisciplinasKicks, | ||
); | ||
} |
Oops, something went wrong.