mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 17:57:45 +03:00
Первый релиз.
Названия конечных точек теперь пишутся в нижнем регистре через знак минуса. У DTO добавлена пара недостающих примеров в документации. Удалён неиспользуемый декоратор IsMap. users.controller.ts - Описана конечная точка "me". - Добавлены конечные точки "change-username" и "change-group", для смены имени пользователя и группы соответственно. users.service.ts - Добавлены методы "changeUsername" и "changeGroup", для смены имени пользователя и группы соответственно.
This commit is contained in:
@@ -51,7 +51,7 @@ export class AuthController {
|
||||
})
|
||||
@ResultDto(SignInResDto)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("signIn")
|
||||
@Post("sign-in")
|
||||
signIn(@Body() signInDto: SignInReqDto) {
|
||||
return this.authService.signIn(signInDto);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export class AuthController {
|
||||
})
|
||||
@ResultDto(SignUpResDto)
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@Post("signUp")
|
||||
@Post("sign-up")
|
||||
async signUp(@Body() signUpDto: SignUpReqDto) {
|
||||
if (
|
||||
!(await this.scheduleService.getGroupNames()).names.includes(
|
||||
@@ -88,7 +88,7 @@ export class AuthController {
|
||||
@ApiExtraModels(UpdateTokenResDto)
|
||||
@ApiOperation({
|
||||
summary: "Обновление просроченного токена",
|
||||
tags: ["auth", "accessToken"],
|
||||
tags: ["auth", "access-token"],
|
||||
})
|
||||
@ApiBody({ schema: refs(UpdateTokenReqDto)[0] })
|
||||
@ApiOkResponse({
|
||||
@@ -100,7 +100,7 @@ export class AuthController {
|
||||
})
|
||||
@ResultDto(UpdateTokenResDto)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("updateToken")
|
||||
@Post("update-token")
|
||||
updateToken(
|
||||
@Body() updateTokenDto: UpdateTokenReqDto,
|
||||
): Promise<UpdateTokenResDto> {
|
||||
@@ -121,7 +121,7 @@ export class AuthController {
|
||||
})
|
||||
@ResultDto(null)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("changePassword")
|
||||
@Post("change-password")
|
||||
async changePassword(
|
||||
@Body() changePasswordReqDto: ChangePasswordReqDto,
|
||||
@UserToken() userToken: string,
|
||||
|
||||
@@ -5,7 +5,10 @@ import { Expose } from "class-transformer";
|
||||
|
||||
// SignIn
|
||||
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
||||
@ApiProperty({ description: "Пароль в исходном виде" })
|
||||
@ApiProperty({
|
||||
example: "my-password",
|
||||
description: "Пароль в исходном виде",
|
||||
})
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, OmitType } from "@nestjs/swagger";
|
||||
import { ApiProperty, OmitType, PickType } from "@nestjs/swagger";
|
||||
import {
|
||||
IsEnum,
|
||||
IsJWT,
|
||||
@@ -16,7 +16,10 @@ export enum UserRoleDto {
|
||||
}
|
||||
|
||||
export class UserDto {
|
||||
@ApiProperty({ description: "Идентификатор (ObjectId)" })
|
||||
@ApiProperty({
|
||||
example: "66e1b7e255c5d5f1268cce90",
|
||||
description: "Идентификатор (ObjectId)",
|
||||
})
|
||||
@IsMongoId()
|
||||
@Expose()
|
||||
id: string;
|
||||
@@ -66,14 +69,20 @@ export class UserDto {
|
||||
role: UserRoleDto;
|
||||
}
|
||||
|
||||
export class ClientUserDto extends OmitType(UserDto, [
|
||||
export class ClientUserResDto extends OmitType(UserDto, [
|
||||
"password",
|
||||
"salt",
|
||||
"accessToken",
|
||||
]) {
|
||||
static fromUserDto(userDto: UserDto): ClientUserDto {
|
||||
return plainToClass(ClientUserDto, userDto, {
|
||||
static fromUserDto(userDto: UserDto): ClientUserResDto {
|
||||
return plainToClass(ClientUserResDto, userDto, {
|
||||
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: "Требуемая группа не найдена" })
|
||||
@ResultDto(GroupScheduleDto)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("getGroup")
|
||||
@Post("get-group")
|
||||
getGroupSchedule(
|
||||
@Body() groupDto: GroupScheduleRequestDto,
|
||||
): Promise<GroupScheduleDto> {
|
||||
@@ -73,7 +73,7 @@ export class ScheduleController {
|
||||
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
|
||||
@ResultDto(ScheduleGroupsDto)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Get("getGroupNames")
|
||||
@Get("get-group-names")
|
||||
async getGroupNames(): Promise<ScheduleGroupsDto> {
|
||||
return this.scheduleService.getGroupNames();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,93 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
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 { UserToken } from "../auth/auth.decorator";
|
||||
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")
|
||||
@UseGuards(AuthGuard)
|
||||
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)
|
||||
@Get("me")
|
||||
async getMe(@UserToken() token: string): Promise<ClientUserDto> {
|
||||
async getMe(@UserToken() token: string): Promise<ClientUserResDto> {
|
||||
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 { UsersController } from "./users.controller";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { ScheduleService } from "../schedule/schedule.service";
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService, UsersService, AuthService],
|
||||
providers: [PrismaService, UsersService, AuthService, ScheduleService],
|
||||
exports: [UsersService],
|
||||
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 { 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()
|
||||
export class UsersService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly scheduleService: ScheduleService,
|
||||
) {}
|
||||
|
||||
private static convertToDto = (user: UserDto | null) =>
|
||||
user as UserDto | null;
|
||||
@@ -38,4 +50,41 @@ export class UsersService {
|
||||
.count({ where })
|
||||
.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