Добавлена совместимость с Firebase Cloud Messaging.

Сервис и контроллер модуля schedule-replacer были перенесены в модуль schedule.
This commit is contained in:
2024-10-05 00:36:50 +04:00
parent 32e06350ad
commit 6ffe39a4a9
24 changed files with 1377 additions and 79 deletions

View File

@@ -149,14 +149,8 @@ export class ScheduleParser {
!downloadData.new &&
this.lastResult &&
this.xlsDownloader.getCacheMode() !== XlsDownloaderCacheMode.NONE
) {
console.debug(
"Так как скачанный XLS не новый, присутствует уже готовый результат и кеширование не отключено...",
);
console.debug("будет возвращён предыдущий результат.");
)
return this.lastResult;
}
const workBook = XLSX.read(downloadData.fileData);
const workSheet = workBook.Sheets[workBook.SheetNames[0]];

View File

@@ -9,7 +9,7 @@ import {
NotAcceptableException,
ServiceUnavailableException,
} from "@nestjs/common";
import { ScheduleReplacerService } from "../../../schedule-replacer/schedule-replacer.service";
import { ScheduleReplacerService } from "../../schedule-replacer.service";
import { Error } from "mongoose";
import * as crypto from "crypto";

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ScheduleReplacerController } from "./schedule-replacer.controller";
describe("ScheduleReplacerController", () => {
let controller: ScheduleReplacerController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ScheduleReplacerController],
}).compile();
controller = module.get<ScheduleReplacerController>(
ScheduleReplacerController,
);
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,106 @@
import {
BadRequestException,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from "@nestjs/common";
import { AuthGuard } from "src/auth/auth.guard";
import {
ClearScheduleReplacerResDto,
ScheduleReplacerResDto,
} from "../dto/schedule-replacer.dto";
import { AuthRoles } from "../auth-role/auth-role.decorator";
import { UserRoleDto } from "../dto/user.dto";
import { ScheduleReplacerService } from "./schedule-replacer.service";
import { ScheduleService } from "./schedule.service";
import { FileInterceptor } from "@nestjs/platform-express";
import {
ApiExtraModels,
ApiOkResponse,
ApiOperation,
refs,
} from "@nestjs/swagger";
import { ResultDto } from "src/utility/validation/class-validator.interceptor";
@Controller("/api/v1/schedule-replacer")
@UseGuards(AuthGuard)
export class ScheduleReplacerController {
constructor(
private readonly scheduleService: ScheduleService,
private readonly scheduleReplaceService: ScheduleReplacerService,
) {}
@ApiOperation({
description: "Замена текущего расписание на новое",
tags: ["schedule", "replacer"],
})
@ApiOkResponse({ description: "Замена прошла успешно" })
@Post("set")
@HttpCode(HttpStatus.OK)
@AuthRoles([UserRoleDto.ADMIN])
@ResultDto(null)
@UseInterceptors(
FileInterceptor("file", { limits: { fileSize: 1024 * 1024 } }),
)
async setSchedule(
@UploadedFile() file: Express.Multer.File,
): Promise<void> {
if (!file) throw new BadRequestException("Файл отсутствует");
if (file.mimetype !== "application/vnd.ms-excel")
throw new BadRequestException("Некорректный тип файла");
const etag = (await this.scheduleService.getSourceSchedule()).etag;
await this.scheduleReplaceService.setByEtag(etag, file.buffer);
await this.scheduleService.refreshCache();
}
@ApiExtraModels(ScheduleReplacerResDto)
@ApiOperation({
description: "Получение списка заменителей расписания",
tags: ["schedule", "replacer"],
})
@ApiOkResponse({ description: "Список получен успешно" }) // TODO: ааа((((
@Get("get")
@HttpCode(HttpStatus.OK)
@AuthRoles([UserRoleDto.ADMIN])
@ResultDto(null) // TODO: Как нибудь сделать проверку в таких случаях
async getReplacers(): Promise<ScheduleReplacerResDto[]> {
const etag = (await this.scheduleService.getSourceSchedule()).etag;
const replacer = await this.scheduleReplaceService.getByEtag(etag);
if (!replacer) return [];
return [
{
etag: replacer.etag,
size: replacer.data.byteLength,
},
];
}
@ApiExtraModels(ClearScheduleReplacerResDto)
@ApiOperation({
description: "Удаление всех замен расписаний",
tags: ["schedule", "replacer"],
})
@ApiOkResponse({
description: "Отчистка прошла успешно",
schema: refs(ClearScheduleReplacerResDto)[0],
})
@Post("clear")
@HttpCode(HttpStatus.OK)
@AuthRoles([UserRoleDto.ADMIN])
@ResultDto(ClearScheduleReplacerResDto)
async clear(): Promise<ClearScheduleReplacerResDto> {
const resDto = { count: await this.scheduleReplaceService.clear() };
await this.scheduleService.refreshCache();
return resDto;
}
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ScheduleReplacerService } from "./schedule-replacer.service";
describe("ScheduleReplacerService", () => {
let service: ScheduleReplacerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ScheduleReplacerService],
}).compile();
service = module.get<ScheduleReplacerService>(ScheduleReplacerService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,56 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { ScheduleReplacerDto } from "../dto/schedule-replacer.dto";
import { plainToClass } from "class-transformer";
@Injectable()
export class ScheduleReplacerService {
constructor(private readonly prismaService: PrismaService) {}
async hasByEtag(etag: string): Promise<boolean> {
return (
(await this.prismaService.scheduleReplace.count({
where: { etag: etag },
})) > 0
);
}
async getByEtag(etag: string): Promise<ScheduleReplacerDto | null> {
const response = await this.prismaService.scheduleReplace.findUnique({
where: { etag: etag },
});
if (response == null) return null;
return plainToClass(ScheduleReplacerDto, response);
}
async clear(): Promise<number> {
const count = await this.prismaService.scheduleReplace.count();
await this.prismaService.scheduleReplace.deleteMany({});
return count;
}
async setByEtag(etag: string, buffer: Buffer): Promise<void> {
if (
(await this.prismaService.scheduleReplace.count({
where: { etag: etag },
})) > 0
) {
await this.prismaService.scheduleReplace.update({
where: { etag: etag },
data: {
data: buffer,
},
});
return;
}
await this.prismaService.scheduleReplace.create({
data: {
etag: etag,
data: buffer,
},
});
}
}

View File

@@ -1,18 +1,16 @@
import { Module } from "@nestjs/common";
import { forwardRef, Module } from "@nestjs/common";
import { ScheduleService } from "./schedule.service";
import { ScheduleController } from "./schedule.controller";
import { UsersService } from "../users/users.service";
import { PrismaService } from "../prisma/prisma.service";
import { ScheduleReplacerService } from "../schedule-replacer/schedule-replacer.service";
import { FirebaseAdminModule } from "../firebase-admin/firebase-admin.module";
import { UsersModule } from "src/users/users.module";
import { ScheduleReplacerService } from "./schedule-replacer.service";
import { ScheduleReplacerController } from "./schedule-replacer.controller";
@Module({
providers: [
ScheduleService,
ScheduleReplacerService,
UsersService,
PrismaService,
],
controllers: [ScheduleController],
imports: [forwardRef(() => UsersModule), FirebaseAdminModule],
providers: [PrismaService, ScheduleService, ScheduleReplacerService],
controllers: [ScheduleController, ScheduleReplacerController],
exports: [ScheduleService],
})
export class ScheduleModule {}

View File

@@ -1,4 +1,8 @@
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
import {
Inject,
Injectable,
NotFoundException,
} from "@nestjs/common";
import {
ScheduleParser,
ScheduleParseResult,
@@ -17,7 +21,8 @@ import { Cache, CACHE_MANAGER } from "@nestjs/cache-manager";
import { instanceToPlain } from "class-transformer";
import { cacheGetOrFill } from "../utility/cache.util";
import * as crypto from "crypto";
import { ScheduleReplacerService } from "../schedule-replacer/schedule-replacer.service";
import { ScheduleReplacerService } from "./schedule-replacer.service";
import { FirebaseAdminService } from "../firebase-admin/firebase-admin.service";
@Injectable()
export class ScheduleService {
@@ -37,6 +42,7 @@ export class ScheduleService {
constructor(
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
private readonly scheduleReplacerService: ScheduleReplacerService,
private readonly firebaseAdminService: FirebaseAdminService,
) {
const xlsDownloader = this.scheduleParser.getXlsDownloader();
@@ -77,8 +83,26 @@ export class ScheduleService {
if (
this.scheduleUpdatedAt.valueOf() === 0 ||
this.cacheHash !== oldHash
)
) {
if (this.scheduleUpdatedAt.valueOf() !== 0) {
const isReplaced =
await this.scheduleReplacerService.hasByEtag(
schedule.etag,
);
await this.firebaseAdminService.sendByTopic(
"schedule-update",
{
data: {
type: "schedule-update",
replaced: isReplaced.toString(),
etag: schedule.etag,
},
},
);
}
this.scheduleUpdatedAt = new Date();
}
return schedule;
});