mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 09:47:46 +03:00
Пред-деплой
This commit is contained in:
@@ -23,11 +23,13 @@ import {
|
|||||||
SignInResDto,
|
SignInResDto,
|
||||||
SignUpReqDto,
|
SignUpReqDto,
|
||||||
SignUpResDto,
|
SignUpResDto,
|
||||||
UpdateTokenDto,
|
ChangePasswordReqDto,
|
||||||
UpdateTokenResultDto,
|
UpdateTokenReqDto,
|
||||||
|
UpdateTokenResDto,
|
||||||
} from "../dto/auth.dto";
|
} from "../dto/auth.dto";
|
||||||
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
||||||
import { ScheduleService } from "../schedule/schedule.service";
|
import { ScheduleService } from "../schedule/schedule.service";
|
||||||
|
import { UserToken } from "./auth.decorator";
|
||||||
|
|
||||||
@Controller("api/v1/auth")
|
@Controller("api/v1/auth")
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
@@ -82,26 +84,52 @@ export class AuthController {
|
|||||||
return this.authService.signUp(signUpDto);
|
return this.authService.signUp(signUpDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiExtraModels(UpdateTokenDto)
|
@ApiExtraModels(UpdateTokenReqDto)
|
||||||
@ApiExtraModels(UpdateTokenResultDto)
|
@ApiExtraModels(UpdateTokenResDto)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Обновление просроченного токена",
|
summary: "Обновление просроченного токена",
|
||||||
tags: ["auth"],
|
tags: ["auth", "accessToken"],
|
||||||
})
|
})
|
||||||
@ApiBody({ schema: refs(UpdateTokenDto)[0] })
|
@ApiBody({ schema: refs(UpdateTokenReqDto)[0] })
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: "Токен обновлён успешно",
|
description: "Токен обновлён успешно",
|
||||||
schema: refs(UpdateTokenResultDto)[0],
|
schema: refs(UpdateTokenResDto)[0],
|
||||||
})
|
})
|
||||||
@ApiNotFoundResponse({
|
@ApiNotFoundResponse({
|
||||||
description: "Передан несуществующий или недействительный токен",
|
description: "Передан несуществующий или недействительный токен",
|
||||||
})
|
})
|
||||||
@ResultDto(UpdateTokenResultDto)
|
@ResultDto(UpdateTokenResDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("updateToken")
|
@Post("updateToken")
|
||||||
updateToken(
|
updateToken(
|
||||||
@Body() updateTokenDto: UpdateTokenDto,
|
@Body() updateTokenDto: UpdateTokenReqDto,
|
||||||
): Promise<UpdateTokenResultDto> {
|
): Promise<UpdateTokenResDto> {
|
||||||
return this.authService.updateToken(updateTokenDto);
|
return this.authService.updateToken(updateTokenDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiExtraModels(ChangePasswordReqDto)
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Обновление пароля",
|
||||||
|
tags: ["auth", "password"],
|
||||||
|
})
|
||||||
|
@ApiBody({ schema: refs(ChangePasswordReqDto)[0] })
|
||||||
|
@ApiOkResponse({ description: "Пароль обновлён успешно" })
|
||||||
|
@ApiConflictResponse({ description: "Пароли идентичны" })
|
||||||
|
@ApiUnauthorizedResponse({
|
||||||
|
description:
|
||||||
|
"Передан неверный текущий пароль или запрос был послан без токена",
|
||||||
|
})
|
||||||
|
@ResultDto(null)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post("changePassword")
|
||||||
|
async changePassword(
|
||||||
|
@Body() changePasswordReqDto: ChangePasswordReqDto,
|
||||||
|
@UserToken() userToken: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.authService
|
||||||
|
.decodeUserToken(userToken)
|
||||||
|
.then((user) =>
|
||||||
|
this.authService.changePassword(user, changePasswordReqDto),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { AuthGuard } from "./auth.guard";
|
|||||||
|
|
||||||
// TODO: Найти применение этой функции
|
// TODO: Найти применение этой функции
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
export const UserToken = createParamDecorator((_, context: ExecutionContext) => {
|
export const UserToken = createParamDecorator(
|
||||||
|
(_, context: ExecutionContext) => {
|
||||||
return AuthGuard.extractTokenFromRequest(
|
return AuthGuard.extractTokenFromRequest(
|
||||||
context.switchToHttp().getRequest(),
|
context.switchToHttp().getRequest(),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -15,17 +15,20 @@ export class AuthGuard implements CanActivate {
|
|||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static extractTokenFromRequest(req: Request): string | null {
|
public static extractTokenFromRequest(req: Request): string {
|
||||||
const [type, token] = req.headers.authorization?.split(" ") ?? [];
|
const [type, token] = req.headers.authorization?.split(" ") ?? [];
|
||||||
return type === "Bearer" ? token : null;
|
|
||||||
|
if (type !== "Bearer" || !token || token.length === 0)
|
||||||
|
throw new UnauthorizedException("Не указан токен!");
|
||||||
|
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const token = AuthGuard.extractTokenFromRequest(request);
|
const token = AuthGuard.extractTokenFromRequest(request);
|
||||||
|
|
||||||
if (!token) throw new UnauthorizedException("Не указан токен!");
|
if (!token)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
!(await this.jwtService.verifyAsync(token)) ||
|
!(await this.jwtService.verifyAsync(token)) ||
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import {
|
|||||||
SignInResDto,
|
SignInResDto,
|
||||||
SignUpReqDto,
|
SignUpReqDto,
|
||||||
SignUpResDto,
|
SignUpResDto,
|
||||||
UpdateTokenDto,
|
ChangePasswordReqDto,
|
||||||
UpdateTokenResultDto,
|
UpdateTokenReqDto,
|
||||||
|
UpdateTokenResDto,
|
||||||
} from "../dto/auth.dto";
|
} from "../dto/auth.dto";
|
||||||
import { UsersService } from "../users/users.service";
|
import { UsersService } from "../users/users.service";
|
||||||
import { genSalt, hash } from "bcrypt";
|
import { genSalt, hash } from "bcrypt";
|
||||||
@@ -27,13 +28,30 @@ export class AuthService {
|
|||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async decodeUserToken(token: string): Promise<UserDto | null> {
|
async decodeUserToken(token: string): Promise<UserDto> {
|
||||||
const jwtUser: { id: string } =
|
const jwtUser: { id: string } | null =
|
||||||
await this.jwtService.verifyAsync(token);
|
await this.jwtService.verifyAsync(token);
|
||||||
|
|
||||||
return this.usersService
|
if (jwtUser === null) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
"Некорректный или недействительный токен",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.usersService
|
||||||
.findUnique({ id: jwtUser.id })
|
.findUnique({ id: jwtUser.id })
|
||||||
.then((user) => user as UserDto | null);
|
.then((user) => user as UserDto | null);
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
throw new UnauthorizedException("Не удалось найти пользователя!");
|
||||||
|
|
||||||
|
if (user.accessToken !== token) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
"Некорректный или недействительный токен",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user as UserDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
async signUp(signUpDto: SignUpReqDto): Promise<SignUpResDto> {
|
async signUp(signUpDto: SignUpReqDto): Promise<SignUpResDto> {
|
||||||
@@ -99,8 +117,8 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateToken(
|
async updateToken(
|
||||||
updateTokenDto: UpdateTokenDto,
|
updateTokenDto: UpdateTokenReqDto,
|
||||||
): Promise<UpdateTokenResultDto> {
|
): Promise<UpdateTokenResDto> {
|
||||||
if (
|
if (
|
||||||
!(await this.jwtService.verifyAsync(updateTokenDto.accessToken, {
|
!(await this.jwtService.verifyAsync(updateTokenDto.accessToken, {
|
||||||
ignoreExpiration: true,
|
ignoreExpiration: true,
|
||||||
@@ -131,4 +149,24 @@ export class AuthService {
|
|||||||
|
|
||||||
return { accessToken: accessToken };
|
return { accessToken: accessToken };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changePassword(
|
||||||
|
user: UserDto,
|
||||||
|
changePasswordReqDto: ChangePasswordReqDto,
|
||||||
|
): Promise<void> {
|
||||||
|
const { oldPassword, newPassword } = changePasswordReqDto;
|
||||||
|
|
||||||
|
if (oldPassword == newPassword)
|
||||||
|
throw new ConflictException("Пароли идентичны");
|
||||||
|
|
||||||
|
if (user.password !== (await hash(oldPassword, user.salt)))
|
||||||
|
throw new UnauthorizedException("Передан неверный исходный пароль");
|
||||||
|
|
||||||
|
await this.usersService.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
password: await hash(newPassword, user.salt),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ApiProperty, IntersectionType, PickType } from "@nestjs/swagger";
|
import { ApiProperty, IntersectionType, 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";
|
||||||
|
|
||||||
// SignIn
|
// SignIn
|
||||||
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
||||||
@@ -20,6 +21,25 @@ export class SignUpReqDto extends IntersectionType(
|
|||||||
export class SignUpResDto extends SignInResDto {}
|
export class SignUpResDto extends SignInResDto {}
|
||||||
|
|
||||||
// Update token
|
// Update token
|
||||||
export class UpdateTokenDto extends PickType(UserDto, ["accessToken"]) {}
|
export class UpdateTokenReqDto extends PickType(UserDto, ["accessToken"]) {}
|
||||||
|
|
||||||
export class UpdateTokenResultDto extends UpdateTokenDto {}
|
export class UpdateTokenResDto extends UpdateTokenReqDto {}
|
||||||
|
|
||||||
|
// Update password
|
||||||
|
export class ChangePasswordReqDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: "my-old-password",
|
||||||
|
description: "Старый пароль",
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@Expose()
|
||||||
|
oldPassword: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: "my-new-password",
|
||||||
|
description: "Новый пароль",
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@Expose()
|
||||||
|
newPassword: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,11 +56,18 @@ export enum LessonTypeDto {
|
|||||||
export class LessonDto {
|
export class LessonDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: LessonTypeDto.DEFAULT,
|
example: LessonTypeDto.DEFAULT,
|
||||||
description: "Тип занятия.",
|
description: "Тип занятия",
|
||||||
})
|
})
|
||||||
@IsEnum(LessonTypeDto)
|
@IsEnum(LessonTypeDto)
|
||||||
type: LessonTypeDto;
|
type: LessonTypeDto;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: 1,
|
||||||
|
description: "Индекс пары, если присутствует",
|
||||||
|
})
|
||||||
|
@IsNumber()
|
||||||
|
defaultIndex: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: "Элементы высшей математики",
|
example: "Элементы высшей математики",
|
||||||
description: "Название занятия",
|
description: "Название занятия",
|
||||||
@@ -95,14 +102,16 @@ export class LessonDto {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
type: LessonTypeDto,
|
type: LessonTypeDto,
|
||||||
|
defaultIndex: number,
|
||||||
time: LessonTimeDto,
|
time: LessonTimeDto,
|
||||||
name: string,
|
name: string,
|
||||||
cabinets: Array<string>,
|
cabinets: Array<string>,
|
||||||
teacherNames: Array<string>,
|
teacherNames: Array<string>,
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = name;
|
this.defaultIndex = defaultIndex;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
|
this.name = name;
|
||||||
this.cabinets = cabinets;
|
this.cabinets = cabinets;
|
||||||
this.teacherNames = teacherNames;
|
this.teacherNames = teacherNames;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export class ScheduleParseResult {
|
|||||||
export class ScheduleParser {
|
export class ScheduleParser {
|
||||||
private lastResult: ScheduleParseResult | null = null;
|
private lastResult: ScheduleParseResult | null = null;
|
||||||
|
|
||||||
public constructor(
|
public constructor(private readonly xlsDownloader: XlsDownloaderBase) {}
|
||||||
private readonly xlsDownloader: XlsDownloaderBase,
|
|
||||||
private readonly group: string,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private static getCellName(
|
private static getCellName(
|
||||||
worksheet: XLSX.Sheet,
|
worksheet: XLSX.Sheet,
|
||||||
@@ -162,14 +159,14 @@ export class ScheduleParser {
|
|||||||
row < daySkeleton.row + rowDistance;
|
row < daySkeleton.row + rowDistance;
|
||||||
++row
|
++row
|
||||||
) {
|
) {
|
||||||
const time = ScheduleParser.getCellName(
|
const time: string | null = ScheduleParser.getCellName(
|
||||||
workSheet,
|
workSheet,
|
||||||
row,
|
row,
|
||||||
lessonTimeColumn,
|
lessonTimeColumn,
|
||||||
)?.replaceAll(" ", "");
|
)?.replaceAll(" ", "");
|
||||||
if (!time || typeof time !== "string") continue;
|
if (!time || typeof time !== "string") continue;
|
||||||
|
|
||||||
const rawName = ScheduleParser.getCellName(
|
const rawName: string | null = ScheduleParser.getCellName(
|
||||||
workSheet,
|
workSheet,
|
||||||
row,
|
row,
|
||||||
groupSkeleton.column,
|
groupSkeleton.column,
|
||||||
@@ -216,6 +213,9 @@ export class ScheduleParser {
|
|||||||
day.lessons.push(
|
day.lessons.push(
|
||||||
new LessonDto(
|
new LessonDto(
|
||||||
type,
|
type,
|
||||||
|
type === LessonTypeDto.DEFAULT
|
||||||
|
? Number.parseInt(time[0])
|
||||||
|
: -1,
|
||||||
LessonTimeDto.fromString(
|
LessonTimeDto.fromString(
|
||||||
type === LessonTypeDto.DEFAULT
|
type === LessonTypeDto.DEFAULT
|
||||||
? time.substring(5)
|
? time.substring(5)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus, Post,
|
HttpStatus,
|
||||||
|
Post,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export class ScheduleService {
|
|||||||
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
|
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
|
||||||
XlsDownloaderCacheMode.SOFT,
|
XlsDownloaderCacheMode.SOFT,
|
||||||
),
|
),
|
||||||
"ИС-214/23",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private lastCacheUpdate: Date = new Date(0);
|
private lastCacheUpdate: Date = new Date(0);
|
||||||
@@ -73,7 +72,7 @@ export class ScheduleService {
|
|||||||
|
|
||||||
async getGroup(group: string): Promise<GroupScheduleDto> {
|
async getGroup(group: string): Promise<GroupScheduleDto> {
|
||||||
const schedule = await this.getSourceSchedule();
|
const schedule = await this.getSourceSchedule();
|
||||||
console.log(schedule);
|
|
||||||
if ((schedule.groups as object)[group] === undefined) {
|
if ((schedule.groups as object)[group] === undefined) {
|
||||||
throw new NotFoundException(
|
throw new NotFoundException(
|
||||||
"Группы с таким названием не существует!",
|
"Группы с таким названием не существует!",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
NotFoundException,
|
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
@@ -22,8 +21,6 @@ export class UsersController {
|
|||||||
@Get("me")
|
@Get("me")
|
||||||
async getMe(@UserToken() token: string): Promise<ClientUserDto> {
|
async getMe(@UserToken() token: string): Promise<ClientUserDto> {
|
||||||
const userDto = await this.authService.decodeUserToken(token);
|
const userDto = await this.authService.decodeUserToken(token);
|
||||||
if (!userDto)
|
|
||||||
throw new NotFoundException("Не удалось найти пользователя!");
|
|
||||||
|
|
||||||
return ClientUserDto.fromUserDto(userDto);
|
return ClientUserDto.fromUserDto(userDto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class UsersService {
|
|||||||
async update(params: {
|
async update(params: {
|
||||||
where: Prisma.UserWhereUniqueInput;
|
where: Prisma.UserWhereUniqueInput;
|
||||||
data: Prisma.UserUpdateInput;
|
data: Prisma.UserUpdateInput;
|
||||||
}): Promise<UserDto | null> {
|
}): Promise<UserDto> {
|
||||||
return this.prismaService.user
|
return this.prismaService.user
|
||||||
.update(params)
|
.update(params)
|
||||||
.then(UsersService.convertToDto);
|
.then(UsersService.convertToDto);
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export class ClassValidatorInterceptor implements NestInterceptor {
|
|||||||
handler.name,
|
handler.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (classDto === null) return returnValue;
|
||||||
|
|
||||||
if (classDto === undefined) {
|
if (classDto === undefined) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Undefined DTO type for function \"${cls.name}::${handler.name}\"!`,
|
`Undefined DTO type for function \"${cls.name}::${handler.name}\"!`,
|
||||||
|
|||||||
Reference in New Issue
Block a user