mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 09:47:46 +03:00
1.3.1
Уведомления об обновлении приложения.
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "schedule-parser-next",
|
"name": "schedule-parser-next",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "schedule-parser-next",
|
"name": "schedule-parser-next",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/cache-manager": "^2.2.2",
|
"@nestjs/cache-manager": "^2.2.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "schedule-parser-next",
|
"name": "schedule-parser-next",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "N08I40K",
|
"author": "N08I40K",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -44,4 +44,6 @@ model User {
|
|||||||
role UserRole
|
role UserRole
|
||||||
//
|
//
|
||||||
fcm FCM?
|
fcm FCM?
|
||||||
|
//
|
||||||
|
version String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export class AuthService {
|
|||||||
}),
|
}),
|
||||||
role: signUpDto.role as UserRole,
|
role: signUpDto.role as UserRole,
|
||||||
group: group,
|
group: group,
|
||||||
|
version: signUpDto.version ?? "1.0.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.usersService.create(input).then((user) => {
|
return this.usersService.create(input).then((user) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { configDotenv } from "dotenv";
|
import { configDotenv } from "dotenv";
|
||||||
|
import * as process from "node:process";
|
||||||
|
|
||||||
configDotenv();
|
configDotenv();
|
||||||
|
|
||||||
@@ -12,9 +13,14 @@ export const httpsConstants = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const apiConstants = {
|
export const apiConstants = {
|
||||||
port: process.env.API_PORT ?? 5050,
|
port: +(process.env.API_PORT ?? 5050),
|
||||||
|
version: process.env.SERVER_VERSION!,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const firebaseConstants = {
|
export const firebaseConstants = {
|
||||||
serviceAccountPath: process.env.FIREBASE_ACCOUNT_PATH!,
|
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 { UserDto } from "./user.dto";
|
||||||
import { IsString } from "class-validator";
|
import { IsString } from "class-validator";
|
||||||
import { Expose } from "class-transformer";
|
import { Expose } from "class-transformer";
|
||||||
@@ -19,6 +24,7 @@ export class SignInResDto extends PickType(UserDto, ["id", "accessToken"]) {}
|
|||||||
export class SignUpReqDto extends IntersectionType(
|
export class SignUpReqDto extends IntersectionType(
|
||||||
SignInReqDto,
|
SignInReqDto,
|
||||||
PickType(UserDto, ["role", "group"]),
|
PickType(UserDto, ["role", "group"]),
|
||||||
|
PartialType(PickType(UserDto, ["version"])),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
export class SignUpResDto extends SignInResDto {}
|
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,
|
IsMongoId,
|
||||||
IsObject,
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
IsSemVer,
|
||||||
IsString,
|
IsString,
|
||||||
MaxLength,
|
MaxLength,
|
||||||
MinLength,
|
MinLength,
|
||||||
@@ -97,6 +98,11 @@ export class UserDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Expose()
|
@Expose()
|
||||||
fcm: UserFcmDto | null;
|
fcm: UserFcmDto | null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: "Версия установленого приложения" })
|
||||||
|
@IsSemVer()
|
||||||
|
@Expose()
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClientUserResDto extends OmitType(UserDto, [
|
export class ClientUserResDto extends OmitType(UserDto, [
|
||||||
@@ -104,6 +110,7 @@ export class ClientUserResDto extends OmitType(UserDto, [
|
|||||||
"salt",
|
"salt",
|
||||||
"accessToken",
|
"accessToken",
|
||||||
"fcm",
|
"fcm",
|
||||||
|
"version",
|
||||||
]) {
|
]) {
|
||||||
static fromUserDto(userDto: UserDto): ClientUserResDto {
|
static fromUserDto(userDto: UserDto): ClientUserResDto {
|
||||||
return plainToClass(ClientUserResDto, userDto, {
|
return plainToClass(ClientUserResDto, userDto, {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
@@ -12,11 +14,13 @@ import { UserFromTokenPipe } from "../auth/auth.pipe";
|
|||||||
import { UserDto } from "../dto/user.dto";
|
import { UserDto } from "../dto/user.dto";
|
||||||
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
||||||
import { FirebaseAdminService } from "./firebase-admin.service";
|
import { FirebaseAdminService } from "./firebase-admin.service";
|
||||||
|
import { FcmPostUpdateDto } from "../dto/fcm.dto";
|
||||||
|
import { isSemVer } from "class-validator";
|
||||||
|
|
||||||
@Controller("api/v1/fcm")
|
@Controller("api/v1/fcm")
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
export class FirebaseAdminController {
|
export class FirebaseAdminController {
|
||||||
private readonly defaultTopics = new Set(["schedule-update"]);
|
private readonly defaultTopics = new Set(["schedule-update", "app-update"]);
|
||||||
|
|
||||||
constructor(private readonly firebaseAdminService: FirebaseAdminService) {}
|
constructor(private readonly firebaseAdminService: FirebaseAdminService) {}
|
||||||
|
|
||||||
@@ -38,4 +42,37 @@ export class FirebaseAdminController {
|
|||||||
this.defaultTopics,
|
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);
|
const currentTopics = new Set(fcm.topics);
|
||||||
|
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
|
if (!fcm.topics.includes(topic)) continue;
|
||||||
|
|
||||||
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
await this.messaging.unsubscribeFromTopic(fcm.token, topic);
|
||||||
currentTopics.delete(topic);
|
currentTopics.delete(topic);
|
||||||
}
|
}
|
||||||
@@ -84,6 +86,8 @@ export class FirebaseAdminService implements OnModuleInit {
|
|||||||
const currentTopics = new Set(fcm.topics);
|
const currentTopics = new Set(fcm.topics);
|
||||||
|
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
|
if (fcm.topics.includes(topic)) continue;
|
||||||
|
|
||||||
await this.messaging.subscribeToTopic(fcm.token, topic);
|
await this.messaging.subscribeToTopic(fcm.token, topic);
|
||||||
currentTopics.add(topic);
|
currentTopics.add(topic);
|
||||||
}
|
}
|
||||||
@@ -96,4 +100,17 @@ export class FirebaseAdminService implements OnModuleInit {
|
|||||||
data: { fcm: fcm },
|
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()
|
const swaggerConfig = new DocumentBuilder()
|
||||||
.setTitle("Schedule Parser")
|
.setTitle("Schedule Parser")
|
||||||
.setDescription("Парсер расписания")
|
.setDescription("Парсер расписания")
|
||||||
.setVersion("1.0")
|
.setVersion(apiConstants.version)
|
||||||
.build();
|
.build();
|
||||||
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
|
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
|
||||||
swaggerDocument.servers = [
|
swaggerDocument.servers = [
|
||||||
{
|
{
|
||||||
url: "http://localhost:3000",
|
url: `https://localhost:${apiConstants.port}`,
|
||||||
description: "Локальный сервер для разработки",
|
description: "Локальный сервер для разработки",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import {
|
import {
|
||||||
ScheduleParser,
|
ScheduleParser,
|
||||||
ScheduleParseResult,
|
ScheduleParseResult,
|
||||||
@@ -23,6 +19,7 @@ import { cacheGetOrFill } from "../utility/cache.util";
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { ScheduleReplacerService } from "./schedule-replacer.service";
|
import { ScheduleReplacerService } from "./schedule-replacer.service";
|
||||||
import { FirebaseAdminService } from "../firebase-admin/firebase-admin.service";
|
import { FirebaseAdminService } from "../firebase-admin/firebase-admin.service";
|
||||||
|
import { scheduleConstants } from "../contants";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScheduleService {
|
export class ScheduleService {
|
||||||
@@ -57,7 +54,8 @@ export class ScheduleService {
|
|||||||
return {
|
return {
|
||||||
cacheHash: this.cacheHash,
|
cacheHash: this.cacheHash,
|
||||||
cacheUpdateRequired:
|
cacheUpdateRequired:
|
||||||
(Date.now() - this.cacheUpdatedAt.valueOf()) / 1000 / 60 >= 5,
|
(Date.now() - this.cacheUpdatedAt.valueOf()) / 1000 / 60 >=
|
||||||
|
scheduleConstants.cacheInvalidateDelay,
|
||||||
lastCacheUpdate: this.cacheUpdatedAt.valueOf(),
|
lastCacheUpdate: this.cacheUpdatedAt.valueOf(),
|
||||||
lastScheduleUpdate: this.scheduleUpdatedAt.valueOf(),
|
lastScheduleUpdate: this.scheduleUpdatedAt.valueOf(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class PartialValidationPipe implements PipeTransform {
|
|||||||
this.partialValidationPipe = new ValidationPipe({
|
this.partialValidationPipe = new ValidationPipe({
|
||||||
...options,
|
...options,
|
||||||
...{
|
...{
|
||||||
skipUndefinedProperties: true,
|
skipUndefinedProperties: false,
|
||||||
skipNullValues: false,
|
skipNullValues: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user