mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 17:57:45 +03:00
1.1.1
Фикс невозможности парсинга субботы. class-validator.interceptor.ts - Добавлена возможность возвращать клиенту любой DTO из списка. Добавлен разный ответ клиенту в зависимости от его версии.
This commit is contained in:
@@ -12,7 +12,13 @@ import {
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { ApiProperty, OmitType, PickType } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
Expose,
|
||||
instanceToPlain,
|
||||
plainToClass,
|
||||
Transform,
|
||||
Type,
|
||||
} from "class-transformer";
|
||||
|
||||
export class LessonTimeDto {
|
||||
@ApiProperty({
|
||||
@@ -205,12 +211,13 @@ export class GroupDto {
|
||||
}
|
||||
}
|
||||
|
||||
export class CacheStatusDto {
|
||||
export class CacheStatusV0Dto {
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: "Нужно ли обновить ссылку для скачивания xls?",
|
||||
})
|
||||
@IsBoolean()
|
||||
@Expose()
|
||||
cacheUpdateRequired: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
@@ -218,9 +225,44 @@ export class CacheStatusDto {
|
||||
description: "Хеш последних полученных данных",
|
||||
})
|
||||
@IsHash("sha1")
|
||||
@Expose()
|
||||
cacheHash: string;
|
||||
}
|
||||
|
||||
export class CacheStatusV1Dto extends CacheStatusV0Dto {
|
||||
@ApiProperty({
|
||||
example: new Date().valueOf(),
|
||||
description: "Дата обновления кеша",
|
||||
})
|
||||
@IsNumber()
|
||||
@Expose()
|
||||
lastCacheUpdate: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: new Date().valueOf(),
|
||||
description: "Дата обновления расписания",
|
||||
})
|
||||
@IsNumber()
|
||||
@Expose()
|
||||
lastScheduleUpdate: number;
|
||||
}
|
||||
|
||||
export class CacheStatusDto extends CacheStatusV1Dto {
|
||||
public static stripVersion(instance: CacheStatusDto, version: number) {
|
||||
switch (version) {
|
||||
default:
|
||||
return instance;
|
||||
case 0: {
|
||||
return plainToClass(
|
||||
CacheStatusV0Dto,
|
||||
instanceToPlain(instance),
|
||||
{ excludeExtraneousValues: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ScheduleDto {
|
||||
@ApiProperty({
|
||||
example: new Date(),
|
||||
|
||||
@@ -107,11 +107,16 @@ export class ScheduleParser {
|
||||
++row;
|
||||
}
|
||||
|
||||
const dayMonthIdx = /[А-Яа-я]+\s(\d+)\.\d+\.\d+/.exec(
|
||||
trimAll(dayName),
|
||||
);
|
||||
if (
|
||||
days.length == 0 ||
|
||||
!days[days.length - 1].name.startsWith("Суббота")
|
||||
) {
|
||||
const dayMonthIdx = /[А-Яа-я]+\s(\d+)\.\d+\.\d+/.exec(
|
||||
trimAll(dayName),
|
||||
);
|
||||
|
||||
if (dayMonthIdx === null) continue;
|
||||
if (dayMonthIdx === null) continue;
|
||||
}
|
||||
|
||||
days.push({
|
||||
row: row,
|
||||
|
||||
@@ -11,6 +11,8 @@ import { AuthGuard } from "../auth/auth.guard";
|
||||
import { ScheduleService } from "./schedule.service";
|
||||
import {
|
||||
CacheStatusDto,
|
||||
CacheStatusV0Dto,
|
||||
CacheStatusV1Dto,
|
||||
GroupScheduleDto,
|
||||
GroupScheduleRequestDto,
|
||||
ScheduleDto,
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
ApiOperation,
|
||||
refs,
|
||||
} from "@nestjs/swagger";
|
||||
import { ClientVersion } from "../version/client-version.decorator";
|
||||
|
||||
@Controller("api/v1/schedule")
|
||||
@UseGuards(AuthGuard)
|
||||
@@ -82,34 +85,59 @@ export class ScheduleController {
|
||||
}
|
||||
|
||||
@ApiExtraModels(SiteMainPageDto)
|
||||
@ApiExtraModels(CacheStatusDto)
|
||||
@ApiExtraModels(CacheStatusV0Dto)
|
||||
@ApiExtraModels(CacheStatusV1Dto)
|
||||
@ApiOperation({
|
||||
summary: "Обновление данных основной страницы политехникума",
|
||||
tags: ["schedule"],
|
||||
})
|
||||
@ApiOkResponse({ description: "Данные обновлены успешно" })
|
||||
@ApiOkResponse({
|
||||
description: "Данные обновлены успешно",
|
||||
schema: refs(CacheStatusV0Dto)[0],
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Данные обновлены успешно",
|
||||
schema: refs(CacheStatusV0Dto)[1],
|
||||
})
|
||||
@ApiNotAcceptableResponse({
|
||||
description: "Передан некорректный код страницы",
|
||||
})
|
||||
@ResultDto(CacheStatusDto)
|
||||
@ResultDto([CacheStatusV0Dto, CacheStatusV1Dto])
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("update-site-main-page")
|
||||
async updateSiteMainPage(
|
||||
@Body() siteMainPageDto: SiteMainPageDto,
|
||||
): Promise<CacheStatusDto> {
|
||||
return await this.scheduleService.updateSiteMainPage(siteMainPageDto);
|
||||
@ClientVersion() version: number,
|
||||
): Promise<CacheStatusV0Dto> {
|
||||
return CacheStatusDto.stripVersion(
|
||||
await this.scheduleService.updateSiteMainPage(siteMainPageDto),
|
||||
version,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiExtraModels(CacheStatusDto)
|
||||
@ApiExtraModels(CacheStatusV0Dto)
|
||||
@ApiExtraModels(CacheStatusV1Dto)
|
||||
@ApiOperation({
|
||||
summary: "Получение информации о кеше",
|
||||
tags: ["schedule", "cache"],
|
||||
})
|
||||
@ApiOkResponse({ description: "Получение данных прошло успешно" })
|
||||
@ResultDto(CacheStatusDto)
|
||||
@ApiOkResponse({
|
||||
description: "Получение данных прошло успешно",
|
||||
schema: refs(CacheStatusV0Dto)[0],
|
||||
})
|
||||
@ApiOkResponse({
|
||||
description: "Получение данных прошло успешно",
|
||||
schema: refs(CacheStatusV1Dto)[0],
|
||||
})
|
||||
@ResultDto([CacheStatusV0Dto, CacheStatusV1Dto])
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Get("cache-status")
|
||||
getCacheStatus(): CacheStatusDto {
|
||||
return this.scheduleService.getCacheStatus();
|
||||
getCacheStatus(
|
||||
@ClientVersion() version: number,
|
||||
): CacheStatusV0Dto | CacheStatusV1Dto {
|
||||
return CacheStatusDto.stripVersion(
|
||||
this.scheduleService.getCacheStatus(),
|
||||
version,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export class ScheduleService {
|
||||
private cacheHash: string = "0000000000000000000000000000000000000000";
|
||||
|
||||
private lastChangedDays: Array<Array<number>> = [];
|
||||
private scheduleUpdatedAt: Date = new Date(0);
|
||||
|
||||
constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {}
|
||||
|
||||
@@ -39,6 +40,8 @@ export class ScheduleService {
|
||||
cacheHash: this.cacheHash,
|
||||
cacheUpdateRequired:
|
||||
(Date.now() - this.cacheUpdatedAt.valueOf()) / 1000 / 60 >= 5,
|
||||
lastCacheUpdate: this.cacheUpdatedAt.valueOf(),
|
||||
lastScheduleUpdate: this.scheduleUpdatedAt.valueOf(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,11 +53,21 @@ export class ScheduleService {
|
||||
) as Array<GroupDto>;
|
||||
|
||||
this.cacheUpdatedAt = new Date();
|
||||
|
||||
const oldHash = this.cacheHash;
|
||||
this.cacheHash = crypto
|
||||
.createHash("sha1")
|
||||
.update(schedule.etag)
|
||||
.update(
|
||||
JSON.stringify(schedule.groups, null, 0) + schedule.etag,
|
||||
)
|
||||
.digest("hex");
|
||||
|
||||
if (
|
||||
this.scheduleUpdatedAt.valueOf() === 0 ||
|
||||
this.cacheHash !== oldHash
|
||||
)
|
||||
this.scheduleUpdatedAt = new Date();
|
||||
|
||||
return schedule;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ export class ClassValidatorInterceptor implements NestInterceptor {
|
||||
handler.name,
|
||||
);
|
||||
|
||||
const isArrayOfDto = Reflect.getMetadata(
|
||||
"design:result-dto-array",
|
||||
cls.prototype,
|
||||
handler.name,
|
||||
);
|
||||
|
||||
if (classDto === null) return returnValue;
|
||||
|
||||
if (classDto === undefined) {
|
||||
@@ -41,41 +47,62 @@ export class ClassValidatorInterceptor implements NestInterceptor {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
const returnValueDto = plainToInstance(
|
||||
classDto,
|
||||
instanceToPlain(returnValue),
|
||||
);
|
||||
const dtoArray: Array<any> = isArrayOfDto
|
||||
? classDto
|
||||
: [classDto];
|
||||
|
||||
if (!(returnValueDto instanceof Object))
|
||||
throw new InternalServerErrorException(
|
||||
returnValueDto,
|
||||
"Return value is not object!",
|
||||
for (let idx = 0; idx < dtoArray.length; idx++) {
|
||||
const returnValueDto = plainToInstance(
|
||||
dtoArray[idx],
|
||||
instanceToPlain(returnValue),
|
||||
);
|
||||
|
||||
const validationErrors = await validate(
|
||||
returnValueDto,
|
||||
this.validatorOptions,
|
||||
);
|
||||
if (!(returnValueDto instanceof Object))
|
||||
throw new InternalServerErrorException(
|
||||
returnValueDto,
|
||||
"Return value is not object!",
|
||||
);
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new UnprocessableEntityException({
|
||||
message: validationErrors
|
||||
.map((value) => Object.values(value.constraints))
|
||||
.flat(),
|
||||
object: returnValue,
|
||||
error: "Response Validation Failed",
|
||||
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
});
|
||||
const validationErrors = await validate(
|
||||
returnValueDto,
|
||||
this.validatorOptions,
|
||||
);
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
if (idx !== dtoArray.length - 1) continue;
|
||||
|
||||
throw new UnprocessableEntityException({
|
||||
message: validationErrors
|
||||
.map((value) =>
|
||||
Object.values(value.constraints),
|
||||
)
|
||||
.flat(),
|
||||
object: returnValue,
|
||||
error: "Response Validation Failed",
|
||||
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
});
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
return returnValue;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// noinspection FunctionNamingConventionJS
|
||||
export function ResultDto(type: any) {
|
||||
export function ResultDto(dtoType: any) {
|
||||
return (target: NonNullable<unknown>, propertyKey: string | symbol) => {
|
||||
Reflect.defineMetadata("design:result-dto", type, target, propertyKey);
|
||||
Reflect.defineMetadata(
|
||||
"design:result-dto",
|
||||
dtoType,
|
||||
target,
|
||||
propertyKey,
|
||||
);
|
||||
Reflect.defineMetadata(
|
||||
"design:result-dto-array",
|
||||
dtoType !== null && dtoType.constructor === Array,
|
||||
target,
|
||||
propertyKey,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
13
src/version/client-version.decorator.ts
Normal file
13
src/version/client-version.decorator.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
||||
|
||||
export const ClientVersion = createParamDecorator(
|
||||
(_, context: ExecutionContext) => {
|
||||
const sourceVersion: string | null = context.switchToHttp().getRequest()
|
||||
.headers.version;
|
||||
const parsedVersion = Number.parseInt(sourceVersion);
|
||||
|
||||
if (Number.isNaN(parsedVersion) || parsedVersion < 0) return 0;
|
||||
|
||||
return parsedVersion;
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user