mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 09:47:46 +03:00
Первый релиз.
Названия конечных точек теперь пишутся в нижнем регистре через знак минуса. У DTO добавлена пара недостающих примеров в документации. Удалён неиспользуемый декоратор IsMap. users.controller.ts - Описана конечная точка "me". - Добавлены конечные точки "change-username" и "change-group", для смены имени пользователя и группы соответственно. users.service.ts - Добавлены методы "changeUsername" и "changeGroup", для смены имени пользователя и группы соответственно.
This commit is contained in:
29
qodana.yaml
Normal file
29
qodana.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
# Qodana analysis is configured by qodana.yaml file #
|
||||||
|
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
version: "1.0"
|
||||||
|
|
||||||
|
#Specify inspection profile for code analysis
|
||||||
|
profile:
|
||||||
|
name: qodana.starter
|
||||||
|
|
||||||
|
#Enable inspections
|
||||||
|
#include:
|
||||||
|
# - name: <SomeEnabledInspectionId>
|
||||||
|
|
||||||
|
#Disable inspections
|
||||||
|
#exclude:
|
||||||
|
# - name: <SomeDisabledInspectionId>
|
||||||
|
# paths:
|
||||||
|
# - <path/where/not/run/inspection>
|
||||||
|
|
||||||
|
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#bootstrap: sh ./prepare-qodana.sh
|
||||||
|
|
||||||
|
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#plugins:
|
||||||
|
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||||
|
|
||||||
|
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||||
|
linter: jetbrains/qodana-js:latest
|
||||||
@@ -51,7 +51,7 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@ResultDto(SignInResDto)
|
@ResultDto(SignInResDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("signIn")
|
@Post("sign-in")
|
||||||
signIn(@Body() signInDto: SignInReqDto) {
|
signIn(@Body() signInDto: SignInReqDto) {
|
||||||
return this.authService.signIn(signInDto);
|
return this.authService.signIn(signInDto);
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@ResultDto(SignUpResDto)
|
@ResultDto(SignUpResDto)
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@Post("signUp")
|
@Post("sign-up")
|
||||||
async signUp(@Body() signUpDto: SignUpReqDto) {
|
async signUp(@Body() signUpDto: SignUpReqDto) {
|
||||||
if (
|
if (
|
||||||
!(await this.scheduleService.getGroupNames()).names.includes(
|
!(await this.scheduleService.getGroupNames()).names.includes(
|
||||||
@@ -88,7 +88,7 @@ export class AuthController {
|
|||||||
@ApiExtraModels(UpdateTokenResDto)
|
@ApiExtraModels(UpdateTokenResDto)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Обновление просроченного токена",
|
summary: "Обновление просроченного токена",
|
||||||
tags: ["auth", "accessToken"],
|
tags: ["auth", "access-token"],
|
||||||
})
|
})
|
||||||
@ApiBody({ schema: refs(UpdateTokenReqDto)[0] })
|
@ApiBody({ schema: refs(UpdateTokenReqDto)[0] })
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
@@ -100,7 +100,7 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@ResultDto(UpdateTokenResDto)
|
@ResultDto(UpdateTokenResDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("updateToken")
|
@Post("update-token")
|
||||||
updateToken(
|
updateToken(
|
||||||
@Body() updateTokenDto: UpdateTokenReqDto,
|
@Body() updateTokenDto: UpdateTokenReqDto,
|
||||||
): Promise<UpdateTokenResDto> {
|
): Promise<UpdateTokenResDto> {
|
||||||
@@ -121,7 +121,7 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@ResultDto(null)
|
@ResultDto(null)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("changePassword")
|
@Post("change-password")
|
||||||
async changePassword(
|
async changePassword(
|
||||||
@Body() changePasswordReqDto: ChangePasswordReqDto,
|
@Body() changePasswordReqDto: ChangePasswordReqDto,
|
||||||
@UserToken() userToken: string,
|
@UserToken() userToken: string,
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { Expose } from "class-transformer";
|
|||||||
|
|
||||||
// SignIn
|
// SignIn
|
||||||
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
||||||
@ApiProperty({ description: "Пароль в исходном виде" })
|
@ApiProperty({
|
||||||
|
example: "my-password",
|
||||||
|
description: "Пароль в исходном виде",
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, OmitType } from "@nestjs/swagger";
|
import { ApiProperty, OmitType, PickType } from "@nestjs/swagger";
|
||||||
import {
|
import {
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsJWT,
|
IsJWT,
|
||||||
@@ -16,7 +16,10 @@ export enum UserRoleDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UserDto {
|
export class UserDto {
|
||||||
@ApiProperty({ description: "Идентификатор (ObjectId)" })
|
@ApiProperty({
|
||||||
|
example: "66e1b7e255c5d5f1268cce90",
|
||||||
|
description: "Идентификатор (ObjectId)",
|
||||||
|
})
|
||||||
@IsMongoId()
|
@IsMongoId()
|
||||||
@Expose()
|
@Expose()
|
||||||
id: string;
|
id: string;
|
||||||
@@ -66,14 +69,20 @@ export class UserDto {
|
|||||||
role: UserRoleDto;
|
role: UserRoleDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClientUserDto extends OmitType(UserDto, [
|
export class ClientUserResDto extends OmitType(UserDto, [
|
||||||
"password",
|
"password",
|
||||||
"salt",
|
"salt",
|
||||||
"accessToken",
|
"accessToken",
|
||||||
]) {
|
]) {
|
||||||
static fromUserDto(userDto: UserDto): ClientUserDto {
|
static fromUserDto(userDto: UserDto): ClientUserResDto {
|
||||||
return plainToClass(ClientUserDto, userDto, {
|
return plainToClass(ClientUserResDto, userDto, {
|
||||||
excludeExtraneousValues: true,
|
excludeExtraneousValues: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// changes
|
||||||
|
|
||||||
|
export class ChangeUsernameReqDto extends PickType(UserDto, ["username"]) {}
|
||||||
|
|
||||||
|
export class ChangeGroupReqDto extends PickType(UserDto, ["group"]) {}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class ScheduleController {
|
|||||||
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
|
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
|
||||||
@ResultDto(GroupScheduleDto)
|
@ResultDto(GroupScheduleDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("getGroup")
|
@Post("get-group")
|
||||||
getGroupSchedule(
|
getGroupSchedule(
|
||||||
@Body() groupDto: GroupScheduleRequestDto,
|
@Body() groupDto: GroupScheduleRequestDto,
|
||||||
): Promise<GroupScheduleDto> {
|
): Promise<GroupScheduleDto> {
|
||||||
@@ -73,7 +73,7 @@ export class ScheduleController {
|
|||||||
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
|
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
|
||||||
@ResultDto(ScheduleGroupsDto)
|
@ResultDto(ScheduleGroupsDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Get("getGroupNames")
|
@Get("get-group-names")
|
||||||
async getGroupNames(): Promise<ScheduleGroupsDto> {
|
async getGroupNames(): Promise<ScheduleGroupsDto> {
|
||||||
return this.scheduleService.getGroupNames();
|
return this.scheduleService.getGroupNames();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,93 @@
|
|||||||
import {
|
import {
|
||||||
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
|
Post,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
import { ClientUserDto } from "../dto/user.dto";
|
import {
|
||||||
|
ChangeGroupReqDto,
|
||||||
|
ChangeUsernameReqDto,
|
||||||
|
ClientUserResDto,
|
||||||
|
} from "../dto/user.dto";
|
||||||
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
||||||
import { UserToken } from "../auth/auth.decorator";
|
import { UserToken } from "../auth/auth.decorator";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
|
import { UsersService } from "./users.service";
|
||||||
|
import {
|
||||||
|
ApiBody,
|
||||||
|
ApiConflictResponse,
|
||||||
|
ApiExtraModels,
|
||||||
|
ApiNotFoundResponse,
|
||||||
|
ApiOkResponse,
|
||||||
|
ApiOperation,
|
||||||
|
refs,
|
||||||
|
} from "@nestjs/swagger";
|
||||||
|
|
||||||
@Controller("api/v1/users")
|
@Controller("api/v1/users")
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@ResultDto(ClientUserDto)
|
@ApiExtraModels(ClientUserResDto)
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Получение данных о профиле пользователя",
|
||||||
|
tags: ["users"],
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: "Получение профиля прошло успешно",
|
||||||
|
schema: refs(ClientUserResDto)[0],
|
||||||
|
})
|
||||||
|
@ResultDto(ClientUserResDto)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Get("me")
|
@Get("me")
|
||||||
async getMe(@UserToken() token: string): Promise<ClientUserDto> {
|
async getMe(@UserToken() token: string): Promise<ClientUserResDto> {
|
||||||
const userDto = await this.authService.decodeUserToken(token);
|
const userDto = await this.authService.decodeUserToken(token);
|
||||||
|
|
||||||
return ClientUserDto.fromUserDto(userDto);
|
return ClientUserResDto.fromUserDto(userDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiExtraModels(ChangeUsernameReqDto)
|
||||||
|
@ApiOperation({ summary: "Смена имени пользователя", tags: ["users"] })
|
||||||
|
@ApiBody({ schema: refs(ChangeUsernameReqDto)[0] })
|
||||||
|
@ApiOkResponse({ description: "Смена имени профиля прошла успешно" })
|
||||||
|
@ApiConflictResponse({
|
||||||
|
description: "Пользователь с таким именем уже существует",
|
||||||
|
})
|
||||||
|
@ResultDto(null)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post("change-username")
|
||||||
|
async changeUsername(
|
||||||
|
@Body() changeUsernameDto: ChangeUsernameReqDto,
|
||||||
|
@UserToken() token: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const user = await this.authService.decodeUserToken(token);
|
||||||
|
|
||||||
|
return await this.usersService.changeUsername(user, changeUsernameDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiExtraModels(ChangeGroupReqDto)
|
||||||
|
@ApiOperation({ summary: "Смена группы пользователя", tags: ["users"] })
|
||||||
|
@ApiBody({ schema: refs(ChangeGroupReqDto)[0] })
|
||||||
|
@ApiOkResponse({ description: "Смена группы прошла успешно" })
|
||||||
|
@ApiNotFoundResponse({
|
||||||
|
description: "Группа с таким названием не существует",
|
||||||
|
})
|
||||||
|
@ResultDto(null)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post("change-group")
|
||||||
|
async changeGroup(
|
||||||
|
@Body() changeGroupDto: ChangeGroupReqDto,
|
||||||
|
@UserToken() token: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const user = await this.authService.decodeUserToken(token);
|
||||||
|
|
||||||
|
return await this.usersService.changeGroup(user, changeGroupDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { UsersService } from "./users.service";
|
|||||||
import { PrismaService } from "../prisma/prisma.service";
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
import { UsersController } from "./users.controller";
|
import { UsersController } from "./users.controller";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
|
import { ScheduleService } from "../schedule/schedule.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [PrismaService, UsersService, AuthService],
|
providers: [PrismaService, UsersService, AuthService, ScheduleService],
|
||||||
exports: [UsersService],
|
exports: [UsersService],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import {
|
||||||
|
ConflictException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from "@nestjs/common";
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { UserDto } from "../dto/user.dto";
|
import {
|
||||||
|
ChangeGroupReqDto,
|
||||||
|
ChangeUsernameReqDto,
|
||||||
|
UserDto,
|
||||||
|
} from "../dto/user.dto";
|
||||||
|
import { ScheduleService } from "../schedule/schedule.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
constructor(private readonly prismaService: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly scheduleService: ScheduleService,
|
||||||
|
) {}
|
||||||
|
|
||||||
private static convertToDto = (user: UserDto | null) =>
|
private static convertToDto = (user: UserDto | null) =>
|
||||||
user as UserDto | null;
|
user as UserDto | null;
|
||||||
@@ -38,4 +50,41 @@ export class UsersService {
|
|||||||
.count({ where })
|
.count({ where })
|
||||||
.then((count) => count > 0);
|
.then((count) => count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changeUsername(
|
||||||
|
user: UserDto,
|
||||||
|
changeUsernameDto: ChangeUsernameReqDto,
|
||||||
|
): Promise<void> {
|
||||||
|
if (user.username === changeUsernameDto.username) return;
|
||||||
|
|
||||||
|
if (await this.contains({ username: changeUsernameDto.username })) {
|
||||||
|
throw new ConflictException(
|
||||||
|
"Пользователь с таким именем уже существует",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: { username: changeUsernameDto.username },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async changeGroup(
|
||||||
|
user: UserDto,
|
||||||
|
changeGroupDto: ChangeGroupReqDto,
|
||||||
|
): Promise<void> {
|
||||||
|
if (user.group === changeGroupDto.group) return;
|
||||||
|
|
||||||
|
const groupNames = await this.scheduleService.getGroupNames();
|
||||||
|
if (!groupNames.names.includes(changeGroupDto.group)) {
|
||||||
|
throw new NotFoundException(
|
||||||
|
"Группа с таким названием не существует",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: { group: changeGroupDto.group },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import {
|
|
||||||
isObject,
|
|
||||||
registerDecorator,
|
|
||||||
ValidationArguments,
|
|
||||||
ValidationOptions,
|
|
||||||
} from "class-validator";
|
|
||||||
|
|
||||||
export function IsMap(
|
|
||||||
keyValidators: ((value: unknown) => boolean)[],
|
|
||||||
valueValidators: ((value: unknown) => boolean)[],
|
|
||||||
validationOptions?: ValidationOptions,
|
|
||||||
) {
|
|
||||||
return function (object: unknown, propertyName: string) {
|
|
||||||
registerDecorator({
|
|
||||||
name: "isMap",
|
|
||||||
target: (object as any).constructor,
|
|
||||||
propertyName: propertyName,
|
|
||||||
options: validationOptions,
|
|
||||||
validator: {
|
|
||||||
validate(value: unknown, args: ValidationArguments): boolean {
|
|
||||||
if (!isObject(value)) return false;
|
|
||||||
const keys = Object.keys(value);
|
|
||||||
const isInvalid = keys.some((key) => {
|
|
||||||
const isKeyInvalid = keyValidators.some(
|
|
||||||
(validator) => !validator(key),
|
|
||||||
);
|
|
||||||
if (isKeyInvalid) return true;
|
|
||||||
|
|
||||||
return valueValidators.some(
|
|
||||||
(validator) => !validator(value[key]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return !isInvalid;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user