mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 17:57:45 +03:00
1.3.1
Уведомления об обновлении приложения.
This commit is contained in:
@@ -83,6 +83,7 @@ export class AuthService {
|
||||
}),
|
||||
role: signUpDto.role as UserRole,
|
||||
group: group,
|
||||
version: signUpDto.version ?? "1.0.0",
|
||||
};
|
||||
|
||||
return this.usersService.create(input).then((user) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { configDotenv } from "dotenv";
|
||||
import * as process from "node:process";
|
||||
|
||||
configDotenv();
|
||||
|
||||
@@ -12,9 +13,14 @@ export const httpsConstants = {
|
||||
};
|
||||
|
||||
export const apiConstants = {
|
||||
port: process.env.API_PORT ?? 5050,
|
||||
port: +(process.env.API_PORT ?? 5050),
|
||||
version: process.env.SERVER_VERSION!,
|
||||
};
|
||||
|
||||
export const firebaseConstants = {
|
||||
serviceAccountPath: process.env.FIREBASE_ACCOUNT_PATH!,
|
||||
};
|
||||
|
||||
export const scheduleConstants = {
|
||||
cacheInvalidateDelay: +(process.env.SERVER_CACHE_INVALIDATE_DELAY! ?? 5),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ApiProperty, IntersectionType, PickType } from "@nestjs/swagger";
|
||||
import {
|
||||
ApiProperty,
|
||||
IntersectionType,
|
||||
PartialType,
|
||||
PickType,
|
||||
} from "@nestjs/swagger";
|
||||
import { UserDto } from "./user.dto";
|
||||
import { IsString } from "class-validator";
|
||||
import { Expose } from "class-transformer";
|
||||
@@ -19,6 +24,7 @@ export class SignInResDto extends PickType(UserDto, ["id", "accessToken"]) {}
|
||||
export class SignUpReqDto extends IntersectionType(
|
||||
SignInReqDto,
|
||||
PickType(UserDto, ["role", "group"]),
|
||||
PartialType(PickType(UserDto, ["version"])),
|
||||
) {}
|
||||
|
||||
export class SignUpResDto extends SignInResDto {}
|
||||
|
||||
17
src/dto/fcm.dto.ts
Normal file
17
src/dto/fcm.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsSemVer, IsUrl } from "class-validator";
|
||||
|
||||
export class FcmPostUpdateDto {
|
||||
@ApiProperty({ example: "1.6.0", description: "Версия приложения" })
|
||||
@IsSemVer()
|
||||
// @Expose()
|
||||
version: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "https://download.host/app-release-1.6.0.apk",
|
||||
description: "Ссылка на приложение",
|
||||
})
|
||||
@IsUrl()
|
||||
// @Expose()
|
||||
downloadLink: string;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IsMongoId,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsSemVer,
|
||||
IsString,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
@@ -97,6 +98,11 @@ export class UserDto {
|
||||
@IsOptional()
|
||||
@Expose()
|
||||
fcm: UserFcmDto | null;
|
||||
|
||||
@ApiProperty({ description: "Версия установленого приложения" })
|
||||
@IsSemVer()
|
||||
@Expose()
|
||||
version: string;
|
||||
}
|
||||
|
||||
export class ClientUserResDto extends OmitType(UserDto, [
|
||||
@@ -104,6 +110,7 @@ export class ClientUserResDto extends OmitType(UserDto, [
|
||||
"salt",
|
||||
"accessToken",
|
||||
"fcm",
|
||||
"version",
|
||||
]) {
|
||||
static fromUserDto(userDto: UserDto): ClientUserResDto {
|
||||
return plainToClass(ClientUserResDto, userDto, {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
@@ -12,11 +14,13 @@ import { UserFromTokenPipe } from "../auth/auth.pipe";
|
||||
import { UserDto } from "../dto/user.dto";
|
||||
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
||||
import { FirebaseAdminService } from "./firebase-admin.service";
|
||||
import { FcmPostUpdateDto } from "../dto/fcm.dto";
|
||||
import { isSemVer } from "class-validator";
|
||||
|
||||
@Controller("api/v1/fcm")
|
||||
@UseGuards(AuthGuard)
|
||||
export class FirebaseAdminController {
|
||||
private readonly defaultTopics = new Set(["schedule-update"]);
|
||||
private readonly defaultTopics = new Set(["schedule-update", "app-update"]);
|
||||
|
||||
constructor(private readonly firebaseAdminService: FirebaseAdminService) {}
|
||||
|
||||
@@ -38,4 +42,37 @@ export class FirebaseAdminController {
|
||||
this.defaultTopics,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("update-callback/:version")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ResultDto(null)
|
||||
async updateCallback(
|
||||
@UserToken(UserFromTokenPipe) userDto: UserDto,
|
||||
@Param("version") version: string,
|
||||
) {
|
||||
if (!isSemVer(version)) {
|
||||
throw new BadRequestException(
|
||||
"version must be a Semantic Versioning Specification",
|
||||
);
|
||||
}
|
||||
|
||||
await this.firebaseAdminService.updateApp(
|
||||
userDto,
|
||||
version,
|
||||
this.defaultTopics,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("post-update")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ResultDto(null)
|
||||
async postUpdate(@Body() postUpdateDto: FcmPostUpdateDto): Promise<void> {
|
||||
await this.firebaseAdminService.sendByTopic("app-update", {
|
||||
data: {
|
||||
type: "app-update",
|
||||
version: postUpdateDto.version,
|
||||
downloadLink: postUpdateDto.downloadLink,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ export class FirebaseAdminService implements OnModuleInit {
|
||||
const currentTopics = new Set(fcm.topics);
|
||||
|
||||
for (const topic of topics) {
|
||||
if (!fcm.topics.includes(topic)) continue;
|
||||
|
||||
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
||||
currentTopics.delete(topic);
|
||||
}
|
||||
@@ -84,6 +86,8 @@ export class FirebaseAdminService implements OnModuleInit {
|
||||
const currentTopics = new Set(fcm.topics);
|
||||
|
||||
for (const topic of topics) {
|
||||
if (fcm.topics.includes(topic)) continue;
|
||||
|
||||
await this.messaging.subscribeToTopic(fcm.token, topic);
|
||||
currentTopics.add(topic);
|
||||
}
|
||||
@@ -96,4 +100,17 @@ export class FirebaseAdminService implements OnModuleInit {
|
||||
data: { fcm: fcm },
|
||||
});
|
||||
}
|
||||
|
||||
async updateApp(
|
||||
userDto: UserDto,
|
||||
version: string,
|
||||
topics: Set<string>,
|
||||
): Promise<void> {
|
||||
await this.subscribe(userDto, topics).then(async (userDto) => {
|
||||
await this.usersService.update({
|
||||
where: { id: userDto.id },
|
||||
data: { version: version },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ async function bootstrap() {
|
||||
const swaggerConfig = new DocumentBuilder()
|
||||
.setTitle("Schedule Parser")
|
||||
.setDescription("Парсер расписания")
|
||||
.setVersion("1.0")
|
||||
.setVersion(apiConstants.version)
|
||||
.build();
|
||||
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
|
||||
swaggerDocument.servers = [
|
||||
{
|
||||
url: "http://localhost:3000",
|
||||
url: `https://localhost:${apiConstants.port}`,
|
||||
description: "Локальный сервер для разработки",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import {
|
||||
ScheduleParser,
|
||||
ScheduleParseResult,
|
||||
@@ -23,6 +19,7 @@ import { cacheGetOrFill } from "../utility/cache.util";
|
||||
import * as crypto from "crypto";
|
||||
import { ScheduleReplacerService } from "./schedule-replacer.service";
|
||||
import { FirebaseAdminService } from "../firebase-admin/firebase-admin.service";
|
||||
import { scheduleConstants } from "../contants";
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleService {
|
||||
@@ -57,7 +54,8 @@ export class ScheduleService {
|
||||
return {
|
||||
cacheHash: this.cacheHash,
|
||||
cacheUpdateRequired:
|
||||
(Date.now() - this.cacheUpdatedAt.valueOf()) / 1000 / 60 >= 5,
|
||||
(Date.now() - this.cacheUpdatedAt.valueOf()) / 1000 / 60 >=
|
||||
scheduleConstants.cacheInvalidateDelay,
|
||||
lastCacheUpdate: this.cacheUpdatedAt.valueOf(),
|
||||
lastScheduleUpdate: this.scheduleUpdatedAt.valueOf(),
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ export class PartialValidationPipe implements PipeTransform {
|
||||
this.partialValidationPipe = new ValidationPipe({
|
||||
...options,
|
||||
...{
|
||||
skipUndefinedProperties: true,
|
||||
skipUndefinedProperties: false,
|
||||
skipNullValues: false,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user