mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 09:47:46 +03:00
3.0.0.
- Updated package version to 3.0.0 - Improved FCM topic handling logic - Enhanced schedule parser accuracy - Removed HTTPS options for dev simplicity - Added detailed API documentation - Removed support for older api versions
This commit is contained in:
@@ -27,7 +27,7 @@ enum UserRole {
|
|||||||
|
|
||||||
type FCM {
|
type FCM {
|
||||||
token String
|
token String
|
||||||
topics Json
|
topics String[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ export class AuthService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получение пользователя по его токену
|
* Получает пользователя по его JWT токену
|
||||||
* @param token - jwt токен
|
* @param {string} token - JWT токен для аутентификации
|
||||||
* @returns {User} - пользователь
|
* @returns {Promise<User>} - Объект пользователя, если токен валиден
|
||||||
* @throws {UnauthorizedException} - некорректный или недействительный токен
|
* @throws {UnauthorizedException} - Если токен некорректен или недействителен
|
||||||
* @throws {UnauthorizedException} - токен указывает на несуществующего пользователя
|
* @throws {UnauthorizedException} - Если пользователь, указанный в токене, не существует
|
||||||
* @throws {UnauthorizedException} - текущий токен устарел и был обновлён на новый
|
* @throws {UnauthorizedException} - Если токен устарел и был заменён на новый
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async decodeUserToken(token: string): Promise<User> {
|
async decodeUserToken(token: string): Promise<User> {
|
||||||
@@ -52,6 +52,13 @@ export class AuthService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Регистрирует нового пользователя в системе.
|
||||||
|
*
|
||||||
|
* @param signUpDto - Объект с данными для регистрации, включая имя пользователя, пароль, роль и группу.
|
||||||
|
* @returns Возвращает объект UserDto с данными зарегистрированного пользователя или объект SignUpErrorDto в случае ошибки.
|
||||||
|
* @throws SignUpErrorDto - Если роль пользователя недопустима или имя пользователя уже существует.
|
||||||
|
*/
|
||||||
async signUp(signUpDto: SignUpDto): Promise<UserDto | SignUpErrorDto> {
|
async signUp(signUpDto: SignUpDto): Promise<UserDto | SignUpErrorDto> {
|
||||||
if (![UserRole.STUDENT, UserRole.TEACHER].includes(signUpDto.role))
|
if (![UserRole.STUDENT, UserRole.TEACHER].includes(signUpDto.role))
|
||||||
return new SignUpErrorDto(SignUpErrorCode.DISALLOWED_ROLE);
|
return new SignUpErrorDto(SignUpErrorCode.DISALLOWED_ROLE);
|
||||||
@@ -75,6 +82,23 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Асинхронная функция для входа пользователя в систему.
|
||||||
|
*
|
||||||
|
* @param {SignInDto} signIn - Объект, содержащий данные для входа (имя пользователя и пароль).
|
||||||
|
* @returns {Promise<UserDto | SignInErrorDto>} - Возвращает объект UserDto в случае успешного входа или SignInErrorDto в случае ошибки.
|
||||||
|
*
|
||||||
|
* @throws {SignInErrorDto} - Если пользователь не найден или пароль неверный, возвращается объект SignInErrorDto с кодом ошибки INCORRECT_CREDENTIALS.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const signInData = { username: 'user123', password: 'password123' };
|
||||||
|
* const result = await signIn(signInData);
|
||||||
|
* if (result instanceof UserDto) {
|
||||||
|
* console.log('Вход выполнен успешно:', result);
|
||||||
|
* } else {
|
||||||
|
* console.log('Ошибка входа:', result);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
async signIn(signIn: SignInDto): Promise<UserDto | SignInErrorDto> {
|
async signIn(signIn: SignInDto): Promise<UserDto | SignInErrorDto> {
|
||||||
const user = await this.usersService.findUnique({
|
const user = await this.usersService.findUnique({
|
||||||
username: signIn.username,
|
username: signIn.username,
|
||||||
@@ -96,6 +120,20 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Парсит VK ID пользователя по access token
|
||||||
|
*
|
||||||
|
* @param accessToken - Access token пользователя VK
|
||||||
|
* @returns Promise, который разрешается в VK ID пользователя или null в случае ошибки
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const vkId = await parseVKID('access_token_here');
|
||||||
|
* if (vkId) {
|
||||||
|
* console.log(`VK ID пользователя: ${vkId}`);
|
||||||
|
* } else {
|
||||||
|
* console.error('Ошибка при получении VK ID');
|
||||||
|
* }
|
||||||
|
*/
|
||||||
private static async parseVKID(accessToken: string): Promise<number> {
|
private static async parseVKID(accessToken: string): Promise<number> {
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append("access_token", accessToken);
|
form.append("access_token", accessToken);
|
||||||
@@ -115,6 +153,12 @@ export class AuthService {
|
|||||||
return data.response.id;
|
return data.response.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Регистрация пользователя через VK
|
||||||
|
* @param signUpDto - DTO с данными для регистрации через VK
|
||||||
|
* @returns Promise<UserDto | SignUpErrorDto> - возвращает DTO пользователя в случае успешной регистрации
|
||||||
|
* или DTO ошибки в случае возникновения проблем
|
||||||
|
*/
|
||||||
async signUpVK(signUpDto: SignUpVKDto): Promise<UserDto | SignUpErrorDto> {
|
async signUpVK(signUpDto: SignUpVKDto): Promise<UserDto | SignUpErrorDto> {
|
||||||
if (![UserRole.STUDENT, UserRole.TEACHER].includes(signUpDto.role))
|
if (![UserRole.STUDENT, UserRole.TEACHER].includes(signUpDto.role))
|
||||||
return new SignUpErrorDto(SignUpErrorCode.DISALLOWED_ROLE);
|
return new SignUpErrorDto(SignUpErrorCode.DISALLOWED_ROLE);
|
||||||
@@ -148,6 +192,11 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Авторизация пользователя через VK
|
||||||
|
* @param signInVKDto - DTO с данными для авторизации через VK
|
||||||
|
* @returns Promise<UserDto | SignInErrorDto> - возвращает DTO пользователя в случае успешной авторизации или DTO ошибки в случае неудачи
|
||||||
|
*/
|
||||||
async signInVK(
|
async signInVK(
|
||||||
signInVKDto: SignInVKDto,
|
signInVKDto: SignInVKDto,
|
||||||
): Promise<UserDto | SignInErrorDto> {
|
): Promise<UserDto | SignInErrorDto> {
|
||||||
@@ -172,11 +221,12 @@ export class AuthService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Смена пароля пользователя
|
* Смена пароля пользователя
|
||||||
* @param user - пользователь
|
* @param user - пользователь, для которого меняется пароль
|
||||||
* @param changePassword - старый и новый пароли
|
* @param changePassword - объект, содержащий старый и новый пароли
|
||||||
* @throws {ConflictException} - пароли идентичны
|
* @throws {ConflictException} - выбрасывается, если старый и новый пароли идентичны
|
||||||
* @throws {UnauthorizedException} - неверный исходный пароль
|
* @throws {UnauthorizedException} - выбрасывается, если передан неверный исходный пароль
|
||||||
* @async
|
* @async
|
||||||
|
* @returns {Promise<void>} - возвращает Promise, который разрешается, когда пароль успешно изменен
|
||||||
*/
|
*/
|
||||||
async changePassword(
|
async changePassword(
|
||||||
user: User,
|
user: User,
|
||||||
@@ -187,13 +237,13 @@ export class AuthService {
|
|||||||
if (oldPassword == newPassword)
|
if (oldPassword == newPassword)
|
||||||
throw new ConflictException("Пароли идентичны");
|
throw new ConflictException("Пароли идентичны");
|
||||||
|
|
||||||
if (user.password !== (await hash(oldPassword, user.salt)))
|
if (!(await compare(oldPassword, user.password)))
|
||||||
throw new UnauthorizedException("Передан неверный исходный пароль");
|
throw new UnauthorizedException("Передан неверный исходный пароль");
|
||||||
|
|
||||||
await this.usersService.update({
|
await this.usersService.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
data: {
|
data: {
|
||||||
password: await hash(newPassword, user.salt),
|
password: await hash(newPassword, await genSalt(8)),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Param,
|
Param, Patch,
|
||||||
Post,
|
Post, Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
@@ -54,11 +54,11 @@ export class FirebaseAdminController {
|
|||||||
status: HttpStatus.OK,
|
status: HttpStatus.OK,
|
||||||
description: "Установка токена удалась",
|
description: "Установка токена удалась",
|
||||||
})
|
})
|
||||||
@Post("set-token/:token")
|
@Patch("set-token")
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ResultDto(null)
|
@ResultDto(null)
|
||||||
async setToken(
|
async setToken(
|
||||||
@Param("token") token: string,
|
@Query("token") token: string,
|
||||||
@UserToken(UserPipe) user: User,
|
@UserToken(UserPipe) user: User,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (user.fcm?.token === token) return;
|
if (user.fcm?.token === token) return;
|
||||||
|
|||||||
@@ -86,17 +86,17 @@ export class FirebaseAdminService implements OnModuleInit {
|
|||||||
if (!user.fcm) throw new Error("User does not have fcm data!");
|
if (!user.fcm) throw new Error("User does not have fcm data!");
|
||||||
|
|
||||||
const fcm = user.fcm;
|
const fcm = user.fcm;
|
||||||
const newTopics = new Set<string>();
|
const userTopics = new Set<string>([...fcm.topics]);
|
||||||
|
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
if (!fcm.topics.includes(topic)) continue;
|
if (!fcm.topics.includes(topic)) continue;
|
||||||
|
|
||||||
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
||||||
newTopics.add(topic);
|
userTopics.delete(topic);
|
||||||
}
|
}
|
||||||
if (newTopics.size === fcm.topics.length) return user;
|
if (userTopics.size === fcm.topics.length) return user;
|
||||||
|
|
||||||
fcm.topics = Array.from(newTopics);
|
fcm.topics = Array.from(userTopics);
|
||||||
|
|
||||||
return await this.usersService.update({
|
return await this.usersService.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
@@ -118,10 +118,12 @@ export class FirebaseAdminService implements OnModuleInit {
|
|||||||
if (force)
|
if (force)
|
||||||
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
||||||
else if (fcm.topics.includes(topic)) continue;
|
else if (fcm.topics.includes(topic)) continue;
|
||||||
else newTopics.add(topic);
|
|
||||||
|
newTopics.add(topic);
|
||||||
|
|
||||||
await this.messaging.subscribeToTopic(fcm.token, topic);
|
await this.messaging.subscribeToTopic(fcm.token, topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newTopics.size === fcm.topics.length) return user;
|
if (newTopics.size === fcm.topics.length) return user;
|
||||||
|
|
||||||
fcm.topics = Array.from(newTopics);
|
fcm.topics = Array.from(newTopics);
|
||||||
|
|||||||
25
src/main.ts
25
src/main.ts
@@ -3,8 +3,7 @@ import { AppModule } from "./app.module";
|
|||||||
import { ValidatorOptions } from "class-validator";
|
import { ValidatorOptions } from "class-validator";
|
||||||
import { PartialValidationPipe } from "./utility/validation/partial-validation.pipe";
|
import { PartialValidationPipe } from "./utility/validation/partial-validation.pipe";
|
||||||
import { ClassValidatorInterceptor } from "./utility/validation/class-validator.interceptor";
|
import { ClassValidatorInterceptor } from "./utility/validation/class-validator.interceptor";
|
||||||
import { apiConstants, httpsConstants } from "./contants";
|
import { apiConstants } from "./contants";
|
||||||
import * as fs from "node:fs";
|
|
||||||
import { VersioningType } from "@nestjs/common";
|
import { VersioningType } from "@nestjs/common";
|
||||||
import {
|
import {
|
||||||
FastifyAdapter,
|
FastifyAdapter,
|
||||||
@@ -16,12 +15,6 @@ async function bootstrap() {
|
|||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
AppModule,
|
AppModule,
|
||||||
new FastifyAdapter(),
|
new FastifyAdapter(),
|
||||||
{
|
|
||||||
httpsOptions: {
|
|
||||||
cert: fs.readFileSync(httpsConstants.certPath),
|
|
||||||
key: fs.readFileSync(httpsConstants.keyPath),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
const validatorOptions: ValidatorOptions = {
|
const validatorOptions: ValidatorOptions = {
|
||||||
enableDebugMessages: true,
|
enableDebugMessages: true,
|
||||||
@@ -33,13 +26,13 @@ async function bootstrap() {
|
|||||||
app.enableCors();
|
app.enableCors();
|
||||||
|
|
||||||
app.setGlobalPrefix("api");
|
app.setGlobalPrefix("api");
|
||||||
app.enableVersioning({
|
app.enableVersioning({ type: VersioningType.URI });
|
||||||
type: VersioningType.URI,
|
|
||||||
});
|
|
||||||
|
|
||||||
const swaggerConfig = new DocumentBuilder()
|
const swaggerConfig = new DocumentBuilder()
|
||||||
.setTitle("Schedule Parser")
|
.setTitle("Schedule Parser")
|
||||||
.setDescription("Парсер расписания")
|
.setDescription(
|
||||||
|
"API для парсинга и управления расписанием учебных занятий",
|
||||||
|
)
|
||||||
.setVersion(apiConstants.version)
|
.setVersion(apiConstants.version)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -49,8 +42,12 @@ async function bootstrap() {
|
|||||||
|
|
||||||
swaggerDocument.servers = [
|
swaggerDocument.servers = [
|
||||||
{
|
{
|
||||||
url: `https://localhost:${apiConstants.port}`,
|
url: "https://polytechnic-dev.n08i40k.ru",
|
||||||
description: "Локальный сервер для разработки",
|
description: "Сервер для разработки и тестирования",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "https://polytechnic.n08i40k.ru",
|
||||||
|
description: "Сервер для продакшн окружения",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ export default class CacheStatusDto {
|
|||||||
cacheUpdateRequired: boolean;
|
cacheUpdateRequired: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Дата обновления кеша
|
* Время последнего обновления кеша в формате timestamp
|
||||||
* @example 1729288173002
|
* @example 1729288173002
|
||||||
*/
|
*/
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
lastCacheUpdate: number;
|
lastCacheUpdate: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Дата обновления расписания
|
* Время последнего обновления расписания в формате timestamp
|
||||||
* @example 1729288173002
|
* @example 1729288173002
|
||||||
*/
|
*/
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
|||||||
@@ -69,13 +69,14 @@ describe("ScheduleParser", () => {
|
|||||||
);
|
);
|
||||||
expect(schedule).toBeDefined();
|
expect(schedule).toBeDefined();
|
||||||
|
|
||||||
const group: Group | undefined = schedule.groups.get("ИС-214/23");
|
const group: Group | undefined = schedule.groups.get("ИС-114/23");
|
||||||
expect(group).toBeDefined();
|
expect(group).toBeDefined();
|
||||||
|
|
||||||
const day = group.days[0];
|
const day = group.days[0];
|
||||||
expect(day).toBeDefined();
|
expect(day).toBeDefined();
|
||||||
|
|
||||||
expect(day.lessons.length).toBeGreaterThan(0);
|
expect(day.lessons.length).toBeGreaterThan(0);
|
||||||
|
expect(day.lessons[0].name).toBe("Линейка");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { XlsDownloaderInterface } from "../xls-downloader/xls-downloader.interfa
|
|||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import { Range, WorkSheet } from "xlsx";
|
import { Range, WorkSheet } from "xlsx";
|
||||||
import { toNormalString, trimAll } from "../../../utility/string.util";
|
import { toNormalString, trimAll } from "../../../utility/string.util";
|
||||||
import { plainToClass, plainToInstance, Type } from "class-transformer";
|
import { plainToInstance, Type } from "class-transformer";
|
||||||
import * as objectHash from "object-hash";
|
import * as objectHash from "object-hash";
|
||||||
import LessonTime from "../../entities/lesson-time.entity";
|
import LessonTime from "../../entities/lesson-time.entity";
|
||||||
import { LessonType } from "../../enum/lesson-type.enum";
|
import { LessonType } from "../../enum/lesson-type.enum";
|
||||||
@@ -607,7 +607,7 @@ export class ScheduleParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static readonly consultationRegExp = /\(?[кК]онсультация\)?/;
|
private static readonly consultationRegExp = /\(?[кК]онсультация\)?/;
|
||||||
private static readonly otherStreetRegExp = /^[А-Я][а-я]+,\s?[0-9]+$/;
|
private static readonly otherStreetRegExp = /^[А-Я][а-я]+,?\s?[0-9]+$/;
|
||||||
|
|
||||||
private static parseLesson(
|
private static parseLesson(
|
||||||
workSheet: XLSX.Sheet,
|
workSheet: XLSX.Sheet,
|
||||||
@@ -689,17 +689,25 @@ export class ScheduleParser {
|
|||||||
column + 1,
|
column + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Если количество кабинетов равно 1, назначаем этот кабинет всем подгруппам
|
||||||
if (cabinets.length === 1) {
|
if (cabinets.length === 1) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||||
for (const index in lessonData.subGroups)
|
for (const index in lessonData.subGroups)
|
||||||
lessonData.subGroups[index].cabinet = cabinets[0];
|
lessonData.subGroups[index].cabinet = cabinets[0] ?? "";
|
||||||
} else if (cabinets.length === lessonData.subGroups.length) {
|
}
|
||||||
|
// Если количество кабинетов совпадает с количеством подгрупп, назначаем кабинеты по порядку
|
||||||
|
else if (cabinets.length === lessonData.subGroups.length) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||||
for (const index in lessonData.subGroups) {
|
for (const index in lessonData.subGroups) {
|
||||||
lessonData.subGroups[index].cabinet =
|
lessonData.subGroups[index].cabinet =
|
||||||
cabinets[lessonData.subGroups[index].number - 1];
|
cabinets[lessonData.subGroups[index].number - 1] ??
|
||||||
|
cabinets[0] ??
|
||||||
|
"";
|
||||||
}
|
}
|
||||||
} else if (cabinets.length !== 0) {
|
}
|
||||||
|
// Если количество кабинетов не равно нулю, но не совпадает с количеством подгрупп
|
||||||
|
else if (cabinets.length !== 0) {
|
||||||
|
// Если кабинетов больше, чем подгрупп, добавляем новые подгруппы с ошибкой
|
||||||
if (cabinets.length > lessonData.subGroups.length) {
|
if (cabinets.length > lessonData.subGroups.length) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||||
for (const index in cabinets) {
|
for (const index in cabinets) {
|
||||||
@@ -717,7 +725,14 @@ export class ScheduleParser {
|
|||||||
|
|
||||||
lessonData.subGroups[index].cabinet = cabinets[index];
|
lessonData.subGroups[index].cabinet = cabinets[index];
|
||||||
}
|
}
|
||||||
} else throw new Error("Разное кол-во кабинетов и подгрупп!");
|
}
|
||||||
|
// Если кабинетов меньше, чем подгрупп, выбрасываем ошибку
|
||||||
|
else throw new Error("Разное кол-во кабинетов и подгрупп!");
|
||||||
|
}
|
||||||
|
// Если кабинетов нет, но есть подгруппы, назначаем им значение "??"
|
||||||
|
else if (lessonData.subGroups.length !== 0) {
|
||||||
|
for (const subGroup of lessonData.subGroups)
|
||||||
|
subGroup.cabinet = "??";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import TeacherSchedule from "./entities/teacher-schedule.entity";
|
|||||||
import GetGroupNamesDto from "./dto/get-group-names.dto";
|
import GetGroupNamesDto from "./dto/get-group-names.dto";
|
||||||
import TeacherNamesDto from "./dto/teacher-names.dto";
|
import TeacherNamesDto from "./dto/teacher-names.dto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сервис для работы с расписанием
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScheduleService {
|
export class ScheduleService {
|
||||||
readonly scheduleParser: ScheduleParser;
|
readonly scheduleParser: ScheduleParser;
|
||||||
@@ -26,6 +29,12 @@ export class ScheduleService {
|
|||||||
|
|
||||||
private scheduleUpdatedAt: Date = new Date(0);
|
private scheduleUpdatedAt: Date = new Date(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор сервиса
|
||||||
|
* @param cacheManager Менеджер кэша
|
||||||
|
* @param scheduleReplacerService Сервис замены расписания
|
||||||
|
* @param firebaseAdminService Сервис работы с Firebase
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
|
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
|
||||||
private readonly scheduleReplacerService: ScheduleReplacerService,
|
private readonly scheduleReplacerService: ScheduleReplacerService,
|
||||||
@@ -54,6 +63,10 @@ export class ScheduleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение статуса кэша
|
||||||
|
* @returns Объект с информацией о состоянии кэша
|
||||||
|
*/
|
||||||
getCacheStatus(): CacheStatusDto {
|
getCacheStatus(): CacheStatusDto {
|
||||||
return plainToInstance(CacheStatusDto, {
|
return plainToInstance(CacheStatusDto, {
|
||||||
cacheHash: this.cacheHash,
|
cacheHash: this.cacheHash,
|
||||||
@@ -65,6 +78,10 @@ export class ScheduleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение исходного расписания
|
||||||
|
* @returns Результат парсинга расписания
|
||||||
|
*/
|
||||||
async getSourceSchedule(): Promise<ScheduleParseResult> {
|
async getSourceSchedule(): Promise<ScheduleParseResult> {
|
||||||
const schedule = await this.scheduleParser.getSchedule();
|
const schedule = await this.scheduleParser.getSchedule();
|
||||||
|
|
||||||
@@ -93,6 +110,10 @@ export class ScheduleService {
|
|||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение расписания
|
||||||
|
* @returns Объект расписания
|
||||||
|
*/
|
||||||
async getSchedule(): Promise<Schedule> {
|
async getSchedule(): Promise<Schedule> {
|
||||||
const sourceSchedule = await this.getSourceSchedule();
|
const sourceSchedule = await this.getSourceSchedule();
|
||||||
|
|
||||||
@@ -103,6 +124,12 @@ export class ScheduleService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение расписания для группы
|
||||||
|
* @param name Название группы
|
||||||
|
* @returns Расписание группы
|
||||||
|
* @throws NotFoundException Если группа не найдена
|
||||||
|
*/
|
||||||
async getGroup(name: string): Promise<GroupSchedule> {
|
async getGroup(name: string): Promise<GroupSchedule> {
|
||||||
const schedule = await this.getSourceSchedule();
|
const schedule = await this.getSourceSchedule();
|
||||||
|
|
||||||
@@ -120,6 +147,10 @@ export class ScheduleService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка названий групп
|
||||||
|
* @returns Объект с массивом названий групп
|
||||||
|
*/
|
||||||
async getGroupNames(): Promise<GetGroupNamesDto> {
|
async getGroupNames(): Promise<GetGroupNamesDto> {
|
||||||
const schedule = await this.getSourceSchedule();
|
const schedule = await this.getSourceSchedule();
|
||||||
const names: Array<string> = [];
|
const names: Array<string> = [];
|
||||||
@@ -131,6 +162,12 @@ export class ScheduleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение расписания для преподавателя
|
||||||
|
* @param name ФИО преподавателя
|
||||||
|
* @returns Расписание преподавателя
|
||||||
|
* @throws NotFoundException Если преподаватель не найден
|
||||||
|
*/
|
||||||
async getTeacher(name: string): Promise<TeacherSchedule> {
|
async getTeacher(name: string): Promise<TeacherSchedule> {
|
||||||
const schedule = await this.getSourceSchedule();
|
const schedule = await this.getSourceSchedule();
|
||||||
|
|
||||||
@@ -148,6 +185,10 @@ export class ScheduleService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка ФИО преподавателей
|
||||||
|
* @returns Объект с массивом ФИО преподавателей
|
||||||
|
*/
|
||||||
async getTeacherNames(): Promise<TeacherNamesDto> {
|
async getTeacherNames(): Promise<TeacherNamesDto> {
|
||||||
const schedule = await this.getSourceSchedule();
|
const schedule = await this.getSourceSchedule();
|
||||||
const names: Array<string> = [];
|
const names: Array<string> = [];
|
||||||
@@ -162,6 +203,11 @@ export class ScheduleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление URL для загрузки расписания
|
||||||
|
* @param url Новый URL
|
||||||
|
* @returns Объект с информацией о состоянии кэша
|
||||||
|
*/
|
||||||
async updateDownloadUrl(url: string): Promise<CacheStatusDto> {
|
async updateDownloadUrl(url: string): Promise<CacheStatusDto> {
|
||||||
await this.scheduleParser.getXlsDownloader().setDownloadUrl(url);
|
await this.scheduleParser.getXlsDownloader().setDownloadUrl(url);
|
||||||
|
|
||||||
@@ -170,6 +216,9 @@ export class ScheduleService {
|
|||||||
return this.getCacheStatus();
|
return this.getCacheStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление кэша
|
||||||
|
*/
|
||||||
async refreshCache() {
|
async refreshCache() {
|
||||||
await this.cacheManager.clear();
|
await this.cacheManager.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,6 @@ export default class User {
|
|||||||
@MaxLength(20)
|
@MaxLength(20)
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Соль пароля
|
|
||||||
* @example "$2b$08$34xwFv1WVJpvpVi3tZZuv."
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
salt: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Хеш пароля
|
* Хеш пароля
|
||||||
* @example "$2b$08$34xwFv1WVJpvpVi3tZZuv."
|
* @example "$2b$08$34xwFv1WVJpvpVi3tZZuv."
|
||||||
|
|||||||
Reference in New Issue
Block a user