Энд-поинт schedule/get-group-names теперь не требует авторизации (для формы регистрации).

Энд-поинт schedule/get-group теперь не требует указания группы. Она берётся из данных пользователя.

Энд-поинт auth/sign-in теперь может возвращать группу пользователя начиная с версии 1.
This commit is contained in:
2024-10-13 17:59:48 +04:00
parent a4d7bb9cdc
commit c594039ca2
10 changed files with 98 additions and 24 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "schedule-parser-next", "name": "schedule-parser-next",
"version": "1.3.5", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "schedule-parser-next", "name": "schedule-parser-next",
"version": "1.3.5", "version": "1.4.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@nestjs/cache-manager": "^2.2.2", "@nestjs/cache-manager": "^2.2.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "schedule-parser-next", "name": "schedule-parser-next",
"version": "1.3.5", "version": "1.4.0",
"description": "", "description": "",
"author": "N08I40K", "author": "N08I40K",
"private": true, "private": true,

View File

@@ -2,3 +2,4 @@ import { Reflector } from "@nestjs/core";
import { UserRoleDto } from "../dto/user.dto"; import { UserRoleDto } from "../dto/user.dto";
export const AuthRoles = Reflector.createDecorator<UserRoleDto[]>(); export const AuthRoles = Reflector.createDecorator<UserRoleDto[]>();
export const AuthUnauthorized = Reflector.createDecorator<true>();

View File

@@ -26,10 +26,13 @@ import {
ChangePasswordReqDto, ChangePasswordReqDto,
UpdateTokenReqDto, UpdateTokenReqDto,
UpdateTokenResDto, UpdateTokenResDto,
SignInResDtoV0,
SignInResDtoV1,
} 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"; import { UserToken } from "./auth.decorator";
import { ResponseVersion } from "../version/response-version.decorator";
@Controller("api/v1/auth") @Controller("api/v1/auth")
export class AuthController { export class AuthController {
@@ -39,21 +42,30 @@ export class AuthController {
) {} ) {}
@ApiExtraModels(SignInReqDto) @ApiExtraModels(SignInReqDto)
@ApiExtraModels(SignInResDto) @ApiExtraModels(SignInResDtoV0)
@ApiExtraModels(SignInResDtoV1)
@ApiOperation({ summary: "Авторизация по логину и паролю", tags: ["auth"] }) @ApiOperation({ summary: "Авторизация по логину и паролю", tags: ["auth"] })
@ApiBody({ schema: refs(SignInReqDto)[0] }) @ApiBody({ schema: refs(SignInReqDto)[0] })
@ApiOkResponse({ @ApiOkResponse({
description: "Авторизация прошла успешно", description: "Авторизация прошла успешно",
schema: refs(SignInResDto)[0], schema: refs(SignInResDtoV0)[0],
})
@ApiOkResponse({
description: "Авторизация прошла успешно",
schema: refs(SignInResDtoV1)[0],
}) })
@ApiUnauthorizedResponse({ @ApiUnauthorizedResponse({
description: "Некорректное имя пользователя или пароль", description: "Некорректное имя пользователя или пароль",
}) })
@ResultDto(SignInResDto) @ResultDto([SignInResDtoV0, SignInResDtoV1])
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("sign-in") @Post("sign-in")
signIn(@Body() signInDto: SignInReqDto) { async signIn(
return this.authService.signIn(signInDto); @Body() signInDto: SignInReqDto,
@ResponseVersion() responseVersion: number,
): Promise<SignInResDtoV1 | SignInResDtoV0> {
const data = await this.authService.signIn(signInDto);
return SignInResDto.stripVersion(data, responseVersion);
} }
@ApiExtraModels(SignUpReqDto) @ApiExtraModels(SignUpReqDto)

View File

@@ -9,7 +9,7 @@ import { JwtService } from "@nestjs/jwt";
import { Request } from "express"; import { Request } from "express";
import { UsersService } from "../users/users.service"; import { UsersService } from "../users/users.service";
import { Reflector } from "@nestjs/core"; import { Reflector } from "@nestjs/core";
import { AuthRoles } from "../auth-role/auth-role.decorator"; import { AuthRoles, AuthUnauthorized } from "../auth-role/auth-role.decorator";
import { isJWT } from "class-validator"; import { isJWT } from "class-validator";
@Injectable() @Injectable()
@@ -30,6 +30,9 @@ export class AuthGuard implements CanActivate {
} }
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
if (this.reflector.get(AuthUnauthorized, context.getHandler()))
return true;
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const token = AuthGuard.extractTokenFromRequest(request); const token = AuthGuard.extractTokenFromRequest(request);

View File

@@ -115,7 +115,7 @@ export class AuthService {
data: { accessToken: accessToken }, data: { accessToken: accessToken },
}); });
return { id: user.id, accessToken: accessToken }; return { id: user.id, accessToken: accessToken, group: user.group };
} }
async updateToken( async updateToken(

View File

@@ -5,8 +5,8 @@ import {
PickType, PickType,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { UserDto } from "./user.dto"; import { UserDto } from "./user.dto";
import { IsString } from "class-validator"; import { IsJWT, IsMongoId, IsString } from "class-validator";
import { Expose } from "class-transformer"; import { Expose, instanceToPlain, plainToClass } from "class-transformer";
// SignIn // SignIn
export class SignInReqDto extends PickType(UserDto, ["username"]) { export class SignInReqDto extends PickType(UserDto, ["username"]) {
@@ -18,7 +18,50 @@ export class SignInReqDto extends PickType(UserDto, ["username"]) {
password: string; password: string;
} }
export class SignInResDto extends PickType(UserDto, ["id", "accessToken"]) {} export class SignInResDtoV0 {
@ApiProperty({
example: "66e1b7e255c5d5f1268cce90",
description: "Идентификатор (ObjectId)",
})
@IsMongoId()
@Expose()
id: string;
@ApiProperty({
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description: "Последний токен доступа",
})
@IsJWT()
@Expose()
accessToken: string;
}
export class SignInResDtoV1 extends SignInResDtoV0 {
@ApiProperty({
example: "ИС-214/23",
description: "Группа",
})
@IsString()
@Expose()
group: string;
}
export class SignInResDto extends SignInResDtoV1 {
public static stripVersion(
instance: SignInResDto,
version: number,
): SignInResDtoV0 | SignInResDtoV1 {
switch (version) {
default:
return instance;
case 0: {
return plainToClass(SignInResDtoV0, instanceToPlain(instance), {
excludeExtraneousValues: true,
});
}
}
}
}
// SignUp // SignUp
export class SignUpReqDto extends IntersectionType( export class SignUpReqDto extends IntersectionType(
@@ -27,7 +70,10 @@ export class SignUpReqDto extends IntersectionType(
PartialType(PickType(UserDto, ["version"])), PartialType(PickType(UserDto, ["version"])),
) {} ) {}
export class SignUpResDto extends SignInResDto {} export class SignUpResDto extends PickType(SignInResDto, [
"id",
"accessToken",
]) {}
// Update token // Update token
export class UpdateTokenReqDto extends PickType(UserDto, ["accessToken"]) {} export class UpdateTokenReqDto extends PickType(UserDto, ["accessToken"]) {}

View File

@@ -11,7 +11,7 @@ import {
IsString, IsString,
ValidateNested, ValidateNested,
} from "class-validator"; } from "class-validator";
import { ApiProperty, OmitType, PickType } from "@nestjs/swagger"; import { ApiProperty, OmitType, PartialType, PickType } from "@nestjs/swagger";
import { import {
Expose, Expose,
instanceToPlain, instanceToPlain,
@@ -296,7 +296,9 @@ export class ScheduleDto {
lastChangedDays: Array<Array<number>>; lastChangedDays: Array<Array<number>>;
} }
export class GroupScheduleRequestDto extends PickType(GroupDto, ["name"]) {} export class GroupScheduleReqDto extends PartialType(
PickType(GroupDto, ["name"]),
) {}
export class ScheduleGroupsDto { export class ScheduleGroupsDto {
@ApiProperty({ @ApiProperty({

View File

@@ -14,7 +14,7 @@ import {
CacheStatusV0Dto, CacheStatusV0Dto,
CacheStatusV1Dto, CacheStatusV1Dto,
GroupScheduleDto, GroupScheduleDto,
GroupScheduleRequestDto, GroupScheduleReqDto,
ScheduleDto, ScheduleDto,
ScheduleGroupsDto, ScheduleGroupsDto,
SiteMainPageDto, SiteMainPageDto,
@@ -28,7 +28,11 @@ import {
ApiOperation, ApiOperation,
refs, refs,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { ClientVersion } from "../version/client-version.decorator"; import { ResponseVersion } from "../version/response-version.decorator";
import { AuthRoles, AuthUnauthorized } from "../auth-role/auth-role.decorator";
import { UserDto, UserRoleDto } from "../dto/user.dto";
import { UserToken } from "../auth/auth.decorator";
import { UserFromTokenPipe } from "../auth/auth.pipe";
@Controller("api/v1/schedule") @Controller("api/v1/schedule")
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@@ -36,12 +40,16 @@ export class ScheduleController {
constructor(private readonly scheduleService: ScheduleService) {} constructor(private readonly scheduleService: ScheduleService) {}
@ApiExtraModels(ScheduleDto) @ApiExtraModels(ScheduleDto)
@ApiOperation({ summary: "Получение расписания", tags: ["schedule"] }) @ApiOperation({
summary: "Получение расписания",
tags: ["schedule", "admin"],
})
@ApiOkResponse({ @ApiOkResponse({
description: "Расписание получено успешно", description: "Расписание получено успешно",
schema: refs(ScheduleDto)[0], schema: refs(ScheduleDto)[0],
}) })
@ResultDto(ScheduleDto) @ResultDto(ScheduleDto)
@AuthRoles([UserRoleDto.ADMIN])
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Get("get") @Get("get")
async getSchedule(): Promise<ScheduleDto> { async getSchedule(): Promise<ScheduleDto> {
@@ -62,9 +70,10 @@ export class ScheduleController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("get-group") @Post("get-group")
async getGroupSchedule( async getGroupSchedule(
@Body() groupDto: GroupScheduleRequestDto, @Body() groupDto: GroupScheduleReqDto,
@UserToken(UserFromTokenPipe) user: UserDto,
): Promise<GroupScheduleDto> { ): Promise<GroupScheduleDto> {
return await this.scheduleService.getGroup(groupDto.name); return await this.scheduleService.getGroup(groupDto.name ?? user.group);
} }
@ApiExtraModels(ScheduleGroupsDto) @ApiExtraModels(ScheduleGroupsDto)
@@ -78,6 +87,7 @@ export class ScheduleController {
}) })
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" }) @ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
@ResultDto(ScheduleGroupsDto) @ResultDto(ScheduleGroupsDto)
@AuthUnauthorized()
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Get("get-group-names") @Get("get-group-names")
async getGroupNames(): Promise<ScheduleGroupsDto> { async getGroupNames(): Promise<ScheduleGroupsDto> {
@@ -107,7 +117,7 @@ export class ScheduleController {
@Post("update-site-main-page") @Post("update-site-main-page")
async updateSiteMainPage( async updateSiteMainPage(
@Body() siteMainPageDto: SiteMainPageDto, @Body() siteMainPageDto: SiteMainPageDto,
@ClientVersion() version: number, @ResponseVersion() version: number,
): Promise<CacheStatusV0Dto> { ): Promise<CacheStatusV0Dto> {
return CacheStatusDto.stripVersion( return CacheStatusDto.stripVersion(
await this.scheduleService.updateSiteMainPage(siteMainPageDto), await this.scheduleService.updateSiteMainPage(siteMainPageDto),
@@ -133,7 +143,7 @@ export class ScheduleController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Get("cache-status") @Get("cache-status")
getCacheStatus( getCacheStatus(
@ClientVersion() version: number, @ResponseVersion() version: number,
): CacheStatusV0Dto | CacheStatusV1Dto { ): CacheStatusV0Dto | CacheStatusV1Dto {
return CacheStatusDto.stripVersion( return CacheStatusDto.stripVersion(
this.scheduleService.getCacheStatus(), this.scheduleService.getCacheStatus(),

View File

@@ -1,6 +1,6 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common"; import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const ClientVersion = createParamDecorator( export const ResponseVersion = createParamDecorator(
(_, context: ExecutionContext) => { (_, context: ExecutionContext) => {
const sourceVersion: string | null = context.switchToHttp().getRequest() const sourceVersion: string | null = context.switchToHttp().getRequest()
.headers.version; .headers.version;