Добавлено получение расписания по имени преподавателя.

This commit is contained in:
2024-11-03 07:04:52 +04:00
parent 2bb09f1f52
commit 7f6ab874db
11 changed files with 3737 additions and 463 deletions

3881
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,7 @@
"eslint": "^8.42.0", "eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0", "jest": "^30.0.0-alpha.6",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"prisma": "^5.19.1", "prisma": "^5.19.1",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",

View File

@@ -10,7 +10,7 @@ import { V2LessonDto } from "./v2-lesson.dto";
export class V2DayDto { export class V2DayDto {
/** /**
* День недели * День недели
* @example Понедельник * @example "Понедельник"
*/ */
@IsString() @IsString()
name: string; name: string;

View File

@@ -0,0 +1,10 @@
import { IsArray } from "class-validator";
export class V2ScheduleTeacherNamesDto {
/**
* Группы
* @example ["Хомченко Н.Е."]
*/
@IsArray()
names: Array<string>;
}

View File

@@ -0,0 +1,15 @@
import { V2DayDto } from "./v2-day.dto";
import { IsArray, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
import { OmitType } from "@nestjs/swagger";
import { V2TeacherLessonDto } from "./v2-teacher-lesson.dto";
export class V2TeacherDayDto extends OmitType(V2DayDto, ["lessons"]) {
/**
* Занятия
*/
@IsArray()
@ValidateNested({ each: true })
@Type(() => V2TeacherLessonDto)
lessons: Array<V2TeacherLessonDto>;
}

View File

@@ -0,0 +1,18 @@
import { V2LessonDto } from "./v2-lesson.dto";
import { IsOptional, IsString } from "class-validator";
import { NullIf } from "../../../utility/class-validators/conditional-field";
import { V2LessonType } from "../../enum/v2-lesson-type.enum";
export class V2TeacherLessonDto extends V2LessonDto {
/**
* Название группы
* @example "ИС-214/23"
* @optional
*/
@IsString()
@IsOptional()
@NullIf((self: V2TeacherLessonDto) => {
return self.type === V2LessonType.BREAK;
})
group: string | null;
}

View File

@@ -0,0 +1,24 @@
import { PickType } from "@nestjs/swagger";
import { V2ScheduleDto } from "./v2-schedule.dto";
import { IsArray, IsObject, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
import { V2TeacherDto } from "./v2-teacher.dto";
export class V2TeacherScheduleDto extends PickType(V2ScheduleDto, [
"updatedAt",
]) {
/**
* Расписание преподавателя
*/
@IsObject()
teacher: V2TeacherDto;
/**
* Обновлённые дни с последнего изменения расписания
* @example [5, 6]
*/
@IsArray()
@ValidateNested({ each: true })
@Type(() => Number)
updated: Array<number>;
}

View File

@@ -0,0 +1,20 @@
import { IsArray, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
import { V2TeacherDayDto } from "./v2-teacher-day.dto";
export class V2TeacherDto {
/**
* ФИО преподавателя
* @example "Хомченко Н.Е."
*/
@IsString()
name: string;
/**
* Расписание каждого дня
*/
@IsArray()
@ValidateNested({ each: true })
@Type(() => V2TeacherDayDto)
days: Array<V2TeacherDayDto>;
}

View File

@@ -13,6 +13,9 @@ import { V2DayDto } from "../../dto/v2/v2-day.dto";
import { V2GroupDto } from "../../dto/v2/v2-group.dto"; import { V2GroupDto } from "../../dto/v2/v2-group.dto";
import * as assert from "node:assert"; import * as assert from "node:assert";
import { ScheduleReplacerService } from "../../schedule-replacer.service"; import { ScheduleReplacerService } from "../../schedule-replacer.service";
import { V2TeacherDto } from "../../dto/v2/v2-teacher.dto";
import { V2TeacherDayDto } from "../../dto/v2/v2-teacher-day.dto";
import { V2TeacherLessonDto } from "../../dto/v2/v2-teacher-lesson.dto";
type InternalId = { type InternalId = {
/** /**
@@ -80,11 +83,23 @@ export class V2ScheduleParseResult {
*/ */
groups: Array<V2GroupDto>; groups: Array<V2GroupDto>;
/**
* Расписание преподавателей в виде списка.
* Ключ - ФИО преподавателя
*/
teachers: Array<V2TeacherDto>;
/** /**
* Список групп у которых было обновлено расписание с момента последнего обновления файла. * Список групп у которых было обновлено расписание с момента последнего обновления файла.
* Ключ - название группы. * Ключ - название группы.
*/ */
updatedGroups: Array<Array<number>>; updatedGroups: Array<Array<number>>;
/**
* Список преподавателей у которых было обновлено расписание с момента последнего обновления файла.
* Ключ - ФИО преподавателя.
*/
updatedTeachers: Array<Array<number>>;
} }
export class V2ScheduleParser { export class V2ScheduleParser {
@@ -323,6 +338,76 @@ export class V2ScheduleParser {
return this.xlsDownloader; return this.xlsDownloader;
} }
private static convertGroupsToTeachers(
groups: Array<V2GroupDto>,
): Array<V2TeacherDto> {
const result: Array<V2TeacherDto> = [];
for (const groupName in groups) {
const group = groups[groupName];
for (const day of group.days) {
for (const lesson of day.lessons) {
if (lesson.type !== V2LessonType.DEFAULT) continue;
for (const subGroup of lesson.subGroups) {
let teacherDto: V2TeacherDto = result[subGroup.teacher];
if (!teacherDto) {
teacherDto = result[subGroup.teacher] =
new V2TeacherDto();
teacherDto.name = subGroup.teacher;
teacherDto.days = [];
}
let teacherDay: V2TeacherDayDto =
teacherDto.days[day.name];
if (!teacherDay) {
teacherDay = teacherDto.days[day.name] =
new V2TeacherDayDto();
// TODO: Что это блять такое?
// noinspection JSConstantReassignment
teacherDay.name = day.name;
teacherDay.date = day.date;
teacherDay.lessons = [];
}
const teacherLesson = structuredClone(
lesson,
) as V2TeacherLessonDto;
teacherLesson.group = groupName;
teacherDay.lessons.push(teacherLesson);
}
}
}
}
for (const teacherName in result) {
const teacher = result[teacherName];
const days = teacher.days;
for (const dayName in days) {
const day = days[dayName];
delete days[dayName];
day.lessons.sort(
(a, b) => a.time.start.valueOf() - b.time.start.valueOf(),
);
days.push(day);
}
days.sort((a, b) => a.date.valueOf() - b.date.valueOf());
}
return result;
}
/** /**
* Возвращает текущее расписание * Возвращает текущее расписание
* @returns {V2ScheduleParseResult} - расписание * @returns {V2ScheduleParseResult} - расписание
@@ -462,11 +547,6 @@ export class V2ScheduleParser {
} }
for (const time of dayTimes) { for (const time of dayTimes) {
// if (day.name === "Четверг" && group.name === "ИС-214/23") {
// console.log("-------------------");
// console.log(groupSkeleton.column);
// console.log(time.xlsxRange);
// }
const lessons = V2ScheduleParser.parseLesson( const lessons = V2ScheduleParser.parseLesson(
workSheet, workSheet,
day, day,
@@ -491,17 +571,32 @@ export class V2ScheduleParser {
groups, groups,
); );
const teachers = V2ScheduleParser.convertGroupsToTeachers(groups);
const updatedTeachers = V2ScheduleParser.getUpdatedTeachers(
this.lastResult?.teachers,
teachers,
);
return (this.lastResult = { return (this.lastResult = {
downloadedAt: headData.requestedAt, downloadedAt: headData.requestedAt,
uploadedAt: headData.uploadedAt, uploadedAt: headData.uploadedAt,
etag: headData.etag, etag: headData.etag,
replacerId: replacer?.id, replacerId: replacer?.id,
groups: groups, groups: groups,
teachers: teachers,
updatedGroups: updatedGroups:
updatedGroups.length === 0 updatedGroups.length === 0
? (this.lastResult?.updatedGroups ?? []) ? (this.lastResult?.updatedGroups ?? [])
: updatedGroups, : updatedGroups,
updatedTeachers:
updatedTeachers.length === 0
? (this.lastResult?.updatedTeachers ?? [])
: updatedTeachers,
}); });
} }
@@ -646,9 +741,9 @@ export class V2ScheduleParser {
const updatedGroups = []; const updatedGroups = [];
for (const groupName in cachedGroups) { for (const name in cachedGroups) {
const cachedGroup = cachedGroups[groupName]; const cachedGroup = cachedGroups[name];
const currentGroup = currentGroups[groupName]; const currentGroup = currentGroups[name];
const affectedGroupDays: Array<number> = []; const affectedGroupDays: Array<number> = [];
@@ -657,12 +752,40 @@ export class V2ScheduleParser {
objectHash.sha1(currentGroup.days[dayIdx]) !== objectHash.sha1(currentGroup.days[dayIdx]) !==
objectHash.sha1(cachedGroup.days[dayIdx]) objectHash.sha1(cachedGroup.days[dayIdx])
) )
affectedGroupDays.push(Number.parseInt(dayIdx)); affectedGroupDays.push(+dayIdx);
} }
updatedGroups[groupName] = affectedGroupDays; updatedGroups[name] = affectedGroupDays;
} }
return updatedGroups; return updatedGroups;
} }
private static getUpdatedTeachers(
cachedTeachers: Array<V2TeacherDto> | null,
currentTeachers: Array<V2TeacherDto>,
): Array<Array<number>> {
if (!cachedTeachers) return [];
const updatedTeachers = [];
for (const name in cachedTeachers) {
const cachedTeacher = cachedTeachers[name];
const currentTeacher = currentTeachers[name];
const affectedTeacherDays: Array<number> = [];
for (const dayIdx in currentTeacher.days) {
if (
objectHash.sha1(currentTeacher.days[dayIdx]) !==
objectHash.sha1(cachedTeacher.days[dayIdx])
)
affectedTeacherDays.push(+dayIdx);
}
updatedTeachers[name] = affectedTeacherDays;
}
return updatedTeachers;
}
} }

View File

@@ -3,7 +3,7 @@ import {
Controller, Controller,
Get, Get,
HttpCode, HttpCode,
HttpStatus, HttpStatus, Param,
Patch, Patch,
UseGuards, UseGuards,
UseInterceptors, UseInterceptors,
@@ -31,6 +31,8 @@ import { V2UpdateDownloadUrlDto } from "./dto/v2/v2-update-download-url.dto";
import { V2GroupScheduleByNameDto } from "./dto/v2/v2-group-schedule-by-name.dto"; import { V2GroupScheduleByNameDto } from "./dto/v2/v2-group-schedule-by-name.dto";
import { V2GroupScheduleDto } from "./dto/v2/v2-group-schedule.dto"; import { V2GroupScheduleDto } from "./dto/v2/v2-group-schedule.dto";
import { V2ScheduleGroupNamesDto } from "./dto/v2/v2-schedule-group-names.dto"; import { V2ScheduleGroupNamesDto } from "./dto/v2/v2-schedule-group-names.dto";
import { V2TeacherScheduleDto } from "./dto/v2/v2-teacher-schedule.dto";
import { V2ScheduleTeacherNamesDto } from "./dto/v2/v2-schedule-teacher-names.dto";
@ApiTags("v2/schedule") @ApiTags("v2/schedule")
@ApiBearerAuth() @ApiBearerAuth()
@@ -85,10 +87,6 @@ export class V2ScheduleController {
description: "Список получен успешно", description: "Список получен успешно",
type: V2ScheduleGroupNamesDto, type: V2ScheduleGroupNamesDto,
}) })
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: "Требуемая группа не найдена",
})
@ResultDto(V2ScheduleGroupNamesDto) @ResultDto(V2ScheduleGroupNamesDto)
@CacheKey("v2-schedule-group-names") @CacheKey("v2-schedule-group-names")
@UseInterceptors(CacheInterceptor) @UseInterceptors(CacheInterceptor)
@@ -99,6 +97,42 @@ export class V2ScheduleController {
return await this.scheduleService.getGroupNames(); return await this.scheduleService.getGroupNames();
} }
@ApiOperation({ summary: "Получение расписания преподавателя" })
@ApiBody({ type: V2GroupScheduleByNameDto })
@ApiResponse({
status: HttpStatus.OK,
description: "Расписание получено успешно",
type: V2TeacherScheduleDto,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: "Требуемый преподаватель не найден",
})
@ResultDto(V2TeacherScheduleDto)
@HttpCode(HttpStatus.OK)
@Get("teacher/:name")
async getTeacherSchedule(
@Param("name") name: string,
): Promise<V2TeacherScheduleDto> {
return await this.scheduleService.getTeacher(name);
}
@ApiOperation({ summary: "Получение списка ФИО преподавателей" })
@ApiResponse({
status: HttpStatus.OK,
description: "Список получен успешно",
type: V2ScheduleTeacherNamesDto,
})
@ResultDto(V2ScheduleTeacherNamesDto)
@CacheKey("v2-schedule-teacher-names")
@UseInterceptors(CacheInterceptor)
@AuthUnauthorized()
@HttpCode(HttpStatus.OK)
@Get("teacher-names")
async getTeacherNames(): Promise<V2ScheduleTeacherNamesDto> {
return await this.scheduleService.getTeacherNames();
}
@ApiOperation({ summary: "Обновление основной страницы политехникума" }) @ApiOperation({ summary: "Обновление основной страницы политехникума" })
@ApiResponse({ @ApiResponse({
status: HttpStatus.OK, status: HttpStatus.OK,

View File

@@ -20,6 +20,8 @@ import * as objectHash from "object-hash";
import { V2CacheStatusDto } from "./dto/v2/v2-cache-status.dto"; import { V2CacheStatusDto } from "./dto/v2/v2-cache-status.dto";
import { V2GroupScheduleDto } from "./dto/v2/v2-group-schedule.dto"; import { V2GroupScheduleDto } from "./dto/v2/v2-group-schedule.dto";
import { V2ScheduleGroupNamesDto } from "./dto/v2/v2-schedule-group-names.dto"; import { V2ScheduleGroupNamesDto } from "./dto/v2/v2-schedule-group-names.dto";
import { V2TeacherScheduleDto } from "./dto/v2/v2-teacher-schedule.dto";
import { V2ScheduleTeacherNamesDto } from "./dto/v2/v2-schedule-teacher-names.dto";
@Injectable() @Injectable()
export class V2ScheduleService { export class V2ScheduleService {
@@ -107,14 +109,14 @@ export class V2ScheduleService {
return { return {
updatedAt: this.cacheUpdatedAt, updatedAt: this.cacheUpdatedAt,
groups: sourceSchedule.groups, groups: sourceSchedule.groups,
updatedGroups: sourceSchedule.updatedGroups, updatedGroups: sourceSchedule.updatedGroups ?? [],
}; };
} }
async getGroup(group: string): Promise<V2GroupScheduleDto> { async getGroup(name: string): Promise<V2GroupScheduleDto> {
const schedule = await this.getSourceSchedule(); const schedule = await this.getSourceSchedule();
if (schedule.groups[group] === undefined) { if (schedule.groups[name] === undefined) {
throw new NotFoundException( throw new NotFoundException(
"Группы с таким названием не существует!", "Группы с таким названием не существует!",
); );
@@ -122,8 +124,8 @@ export class V2ScheduleService {
return { return {
updatedAt: this.cacheUpdatedAt, updatedAt: this.cacheUpdatedAt,
group: schedule.groups[group], group: schedule.groups[name],
updated: schedule.updatedGroups[group] ?? [], updated: schedule.updatedGroups[name] ?? [],
}; };
} }
@@ -131,13 +133,40 @@ export class V2ScheduleService {
const schedule = await this.getSourceSchedule(); const schedule = await this.getSourceSchedule();
const names: Array<string> = []; const names: Array<string> = [];
for (const groupName in schedule.groups) names.push(groupName); for (const name in schedule.groups) names.push(name);
return plainToInstance(V2ScheduleGroupNamesDto, { return plainToInstance(V2ScheduleGroupNamesDto, {
names: names, names: names,
}); });
} }
async getTeacher(name: string): Promise<V2TeacherScheduleDto> {
const schedule = await this.getSourceSchedule();
if (schedule.teachers[name] === undefined) {
throw new NotFoundException(
"Преподавателя с таким ФИО не существует!",
);
}
return {
updatedAt: this.cacheUpdatedAt,
teacher: schedule.teachers[name],
updated: schedule.updatedGroups[name] ?? [],
};
}
async getTeacherNames(): Promise<V2ScheduleTeacherNamesDto> {
const schedule = await this.getSourceSchedule();
const names: Array<string> = [];
for (const name in schedule.teachers) names.push(name);
return plainToInstance(V2ScheduleTeacherNamesDto, {
names: names,
});
}
async updateDownloadUrl( async updateDownloadUrl(
url: string, url: string,
silent: boolean = false, silent: boolean = false,