first commit

This commit is contained in:
lendry
2026-06-24 14:37:15 +03:00
commit 995adeedd4
188 changed files with 28810 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
import { IsIn, IsNumber, IsOptional, IsString, IsUUID, MinLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class UpsertAddressDto {
@ApiPropertyOptional({ description: 'ID адреса для обновления' })
@IsOptional()
@IsUUID('4', { message: 'ID адреса должен быть UUID' })
addressId?: string;
@ApiProperty({ description: 'Тип адреса', enum: ['HOME', 'WORK', 'OTHER'] })
@IsIn(['HOME', 'WORK', 'OTHER'], { message: 'Некорректный тип адреса' })
label!: 'HOME' | 'WORK' | 'OTHER';
@ApiProperty({ description: 'Город' })
@IsString({ message: 'Город должен быть строкой' })
@MinLength(1, { message: 'Укажите город' })
city!: string;
@ApiProperty({ description: 'Улица' })
@IsString({ message: 'Улица должна быть строкой' })
@MinLength(1, { message: 'Укажите улицу' })
street!: string;
@ApiProperty({ description: 'Дом' })
@IsString({ message: 'Дом должен быть строкой' })
@MinLength(1, { message: 'Укажите дом' })
house!: string;
@ApiPropertyOptional({ description: 'Квартира или офис' })
@IsOptional()
@IsString({ message: 'Квартира должна быть строкой' })
apartment?: string;
@ApiPropertyOptional({ description: 'Комментарий' })
@IsOptional()
@IsString({ message: 'Комментарий должен быть строкой' })
comment?: string;
@ApiPropertyOptional({ description: 'Широта' })
@IsOptional()
@IsNumber({}, { message: 'Широта должна быть числом' })
latitude?: number;
@ApiPropertyOptional({ description: 'Долгота' })
@IsOptional()
@IsNumber({}, { message: 'Долгота должна быть числом' })
longitude?: number;
@ApiPropertyOptional({ description: 'Полный адрес одной строкой' })
@IsOptional()
@IsString({ message: 'Полный адрес должен быть строкой' })
fullAddress?: string;
}

View File

@@ -0,0 +1,60 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsEmail, IsEnum, IsOptional, IsString, Matches, MinLength } from 'class-validator';
export enum GatewayUserStatus {
ACTIVE = 'ACTIVE',
SUSPENDED = 'SUSPENDED',
DELETED = 'DELETED'
}
export class ListUsersQueryDto {
@ApiPropertyOptional({ description: 'Поиск по почте, телефону, имени или логину' })
@IsOptional()
@IsString({ message: 'Поисковая строка должна быть текстом' })
search?: string;
}
export class UpdateUserDto {
@ApiPropertyOptional({ description: 'Отображаемое имя' })
@IsOptional()
@IsString({ message: 'Имя должно быть строкой' })
displayName?: string;
@ApiPropertyOptional({ description: 'Основная почта' })
@IsOptional()
@IsEmail({}, { message: 'Укажите корректную почту' })
email?: string;
@ApiPropertyOptional({ description: 'Основной телефон' })
@IsOptional()
@Matches(/^\+?[1-9]\d{7,14}$/, { message: 'Укажите корректный телефон' })
phone?: string;
@ApiPropertyOptional({ description: 'Резервная почта' })
@IsOptional()
@IsEmail({}, { message: 'Укажите корректную резервную почту' })
backupEmail?: string;
@ApiPropertyOptional({ description: 'Резервный телефон' })
@IsOptional()
@Matches(/^\+?[1-9]\d{7,14}$/, { message: 'Укажите корректный резервный телефон' })
backupPhone?: string;
@ApiPropertyOptional({ description: 'Статус пользователя', enum: GatewayUserStatus })
@IsOptional()
@IsEnum(GatewayUserStatus, { message: 'Укажите корректный статус пользователя' })
status?: GatewayUserStatus;
}
export class ResetPasswordDto {
@ApiProperty({ description: 'Новый пароль пользователя', minLength: 8 })
@IsString({ message: 'Пароль должен быть строкой' })
@MinLength(8, { message: 'Пароль должен содержать минимум 8 символов' })
newPassword!: string;
}
export class SetSuperAdminDto {
@ApiProperty({ description: 'Выдать или снять права супер-администратора' })
@IsBoolean({ message: 'isSuperAdmin должно быть boolean' })
isSuperAdmin!: boolean;
}

View File

@@ -0,0 +1,189 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsOptional, IsString, Length, Matches, MinLength } from 'class-validator';
const EMAIL_OR_PHONE_PATTERN = /^([^\s@]+@[^\s@]+\.[^\s@]+|\+?[1-9]\d{9,14})$/;
export class RegisterDto {
@ApiPropertyOptional({ description: 'Основная почта пользователя', example: 'user@example.com' })
@IsOptional()
@IsEmail({}, { message: 'Укажите корректную почту' })
email?: string;
@ApiPropertyOptional({ description: 'Основной телефон пользователя', example: '+79990000000' })
@IsOptional()
@Matches(/^\+?[1-9]\d{7,14}$/, { message: 'Укажите корректный номер телефона' })
phone?: string;
@ApiPropertyOptional({ description: 'Пароль пользователя. Не требуется для passwordless-регистрации.', minLength: 8 })
@IsOptional()
@IsString({ message: 'Пароль должен быть строкой' })
@MinLength(8, { message: 'Пароль должен содержать минимум 8 символов' })
password?: string;
@ApiProperty({ description: 'Имя, которое отображается в профиле', example: 'Иван Петров' })
@IsString({ message: 'Имя должно быть строкой' })
@IsNotEmpty({ message: 'Укажите имя профиля' })
displayName!: string;
@ApiPropertyOptional({ description: 'Публичный логин пользователя', example: 'ivan' })
@IsOptional()
@IsString({ message: 'Логин должен быть строкой' })
username?: string;
}
export class LoginDto {
@ApiProperty({ description: 'Почта, телефон или логин', example: 'user@example.com' })
@IsString({ message: 'Логин должен быть строкой' })
@IsNotEmpty({ message: 'Укажите логин' })
login!: string;
@ApiProperty({ description: 'Пароль пользователя' })
@IsString({ message: 'Пароль должен быть строкой' })
@IsNotEmpty({ message: 'Укажите пароль' })
password!: string;
@ApiProperty({ description: 'Уникальный отпечаток устройства' })
@IsString({ message: 'Отпечаток устройства должен быть строкой' })
@IsNotEmpty({ message: 'Передайте отпечаток устройства' })
fingerprint!: string;
@ApiPropertyOptional({ description: 'Название устройства', example: 'Chrome на Windows' })
@IsOptional()
@IsString({ message: 'Название устройства должно быть строкой' })
deviceName?: string;
@ApiPropertyOptional({ description: 'Тип устройства', example: 'WEB' })
@IsOptional()
@IsString({ message: 'Тип устройства должен быть строкой' })
deviceType?: string;
}
export class IdentifyDto {
@ApiProperty({ description: 'Почта или телефон пользователя', example: 'user@example.com' })
@IsString({ message: 'Логин должен быть строкой' })
@IsNotEmpty({ message: 'Укажите почту или телефон' })
@Matches(EMAIL_OR_PHONE_PATTERN, { message: 'Укажите корректную почту или телефон в формате +79991234567' })
login!: string;
}
export class PasswordlessOtpDto {
@ApiProperty({ description: 'Почта или телефон для входа', example: 'user@example.com' })
@IsString({ message: 'Получатель должен быть строкой' })
@IsNotEmpty({ message: 'Укажите почту или телефон' })
@Matches(EMAIL_OR_PHONE_PATTERN, { message: 'Укажите корректную почту или телефон в формате +79991234567' })
recipient!: string;
@ApiPropertyOptional({ description: 'Альтернативный канал доставки кода', enum: ['primary', 'email', 'phone', 'backupEmail', 'backupPhone'] })
@IsOptional()
@IsString({ message: 'Канал должен быть строкой' })
channel?: string;
}
export class PasswordlessVerifyDto {
@ApiProperty({ description: 'Почта или телефон, на который отправлен код', example: 'user@example.com' })
@IsString({ message: 'Получатель должен быть строкой' })
@Matches(EMAIL_OR_PHONE_PATTERN, { message: 'Укажите корректную почту или телефон в формате +79991234567' })
recipient!: string;
@ApiProperty({ description: 'OTP-код из 6 цифр', example: '123456' })
@IsString({ message: 'Код должен быть строкой' })
@Length(6, 6, { message: 'Код должен содержать 6 цифр' })
@Matches(/^\d+$/, { message: 'Код должен содержать только цифры' })
code!: string;
@ApiProperty({ description: 'Уникальный отпечаток устройства' })
@IsString({ message: 'Отпечаток устройства должен быть строкой' })
fingerprint!: string;
@ApiPropertyOptional({ description: 'Название устройства' })
@IsOptional()
@IsString({ message: 'Название устройства должно быть строкой' })
deviceName?: string;
@ApiPropertyOptional({ description: 'Тип устройства', example: 'WEB' })
@IsOptional()
@IsString({ message: 'Тип устройства должен быть строкой' })
deviceType?: string;
}
export class PasswordLoginDto {
@ApiPropertyOptional({ description: 'Почта или телефон пользователя для identifier-first входа' })
@IsOptional()
@IsString({ message: 'Логин должен быть строкой' })
@Matches(EMAIL_OR_PHONE_PATTERN, { message: 'Укажите корректную почту или телефон в формате +79991234567' })
login?: string;
@ApiPropertyOptional({ description: 'Временный auth_token после успешной OTP-проверки, если используется legacy challenge flow' })
@IsOptional()
@IsString({ message: 'Временный токен должен быть строкой' })
tempAuthToken?: string;
@ApiProperty({ description: 'Пароль пользователя' })
@IsString({ message: 'Пароль должен быть строкой' })
@IsNotEmpty({ message: 'Укажите пароль' })
password!: string;
@ApiProperty({ description: 'Уникальный отпечаток устройства' })
@IsString({ message: 'Отпечаток устройства должен быть строкой' })
fingerprint!: string;
@ApiPropertyOptional({ description: 'Название устройства' })
@IsOptional()
@IsString({ message: 'Название устройства должно быть строкой' })
deviceName?: string;
@ApiPropertyOptional({ description: 'Тип устройства', example: 'WEB' })
@IsOptional()
@IsString({ message: 'Тип устройства должен быть строкой' })
deviceType?: string;
}
export class VerifyPinDto {
@ApiProperty({ description: 'ID временно заблокированной сессии' })
@IsString({ message: 'ID сессии должен быть строкой' })
sessionId!: string;
@ApiProperty({ description: 'PIN-код из 4-6 цифр', example: '1234' })
@IsString({ message: 'PIN-код должен быть строкой' })
@Length(4, 6, { message: 'PIN-код должен содержать от 4 до 6 символов' })
@Matches(/^\d+$/, { message: 'PIN-код должен содержать только цифры' })
pin!: string;
}
export class RefreshSessionDto {
@ApiProperty({ description: 'Refresh token текущей сессии' })
@IsString({ message: 'Refresh token должен быть строкой' })
@IsNotEmpty({ message: 'Передайте refresh token' })
refreshToken!: string;
@ApiPropertyOptional({ description: 'ID сессии для ускоренной проверки' })
@IsOptional()
@IsString({ message: 'ID сессии должен быть строкой' })
sessionId?: string;
}
export class LdapLoginDto {
@ApiProperty({ description: 'Логин LDAP (sAMAccountName, uid, mail и т.д.)', example: 'ivan.petrov' })
@IsString({ message: 'Логин должен быть строкой' })
@IsNotEmpty({ message: 'Укажите LDAP-логин' })
username!: string;
@ApiProperty({ description: 'Пароль LDAP' })
@IsString({ message: 'Пароль должен быть строкой' })
@IsNotEmpty({ message: 'Укажите пароль' })
password!: string;
@ApiProperty({ description: 'Уникальный отпечаток устройства' })
@IsString({ message: 'Отпечаток устройства должен быть строкой' })
fingerprint!: string;
@ApiPropertyOptional({ description: 'Название устройства' })
@IsOptional()
@IsString({ message: 'Название устройства должно быть строкой' })
deviceName?: string;
@ApiPropertyOptional({ description: 'Тип устройства', example: 'WEB' })
@IsOptional()
@IsString({ message: 'Тип устройства должен быть строкой' })
deviceType?: string;
}

View File

@@ -0,0 +1,68 @@
import { IsArray, IsBoolean, IsIn, IsOptional, IsString, MinLength } from 'class-validator';
export class CreateChatRoomDto {
@IsString()
@MinLength(1)
name!: string;
@IsOptional()
@IsArray()
@IsString({ each: true })
memberUserIds?: string[];
}
export class UpdateChatRoomDto {
@IsOptional()
@IsString()
@MinLength(1)
name?: string;
@IsOptional()
@IsBoolean()
notificationsMuted?: boolean;
}
export class SendChatMessageDto {
@IsString()
@IsIn(['TEXT', 'IMAGE', 'AUDIO', 'VOICE', 'FILE', 'EMOJI', 'POLL'])
type!: string;
@IsOptional()
@IsString()
content?: string;
@IsOptional()
@IsString()
replyToId?: string;
@IsOptional()
@IsString()
storageKey?: string;
@IsOptional()
@IsString()
mimeType?: string;
@IsOptional()
@IsString()
metadataJson?: string;
@IsOptional()
poll?: {
question: string;
options: string[];
allowsMultiple?: boolean;
isAnonymous?: boolean;
};
}
export class VotePollDto {
@IsArray()
@IsString({ each: true })
optionIds!: string[];
}
export class SetRoomNotificationsMutedDto {
@IsBoolean()
muted!: boolean;
}

View File

@@ -0,0 +1,62 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsDateString, IsEnum, IsJSON, IsOptional, IsString } from 'class-validator';
export enum DocumentTypeDto {
PASSPORT_RF = 'PASSPORT_RF',
FOREIGN_PASSPORT = 'FOREIGN_PASSPORT',
BIRTH_CERTIFICATE = 'BIRTH_CERTIFICATE',
DRIVER_LICENSE = 'DRIVER_LICENSE',
VEHICLE_REGISTRATION = 'VEHICLE_REGISTRATION',
OMS = 'OMS',
DMS = 'DMS',
INN = 'INN',
SNILS = 'SNILS'
}
export class CreateDocumentDto {
@ApiProperty({ description: 'Тип документа', enum: DocumentTypeDto, example: DocumentTypeDto.PASSPORT_RF })
@IsEnum(DocumentTypeDto, { message: 'Некорректный тип документа' })
type!: DocumentTypeDto;
@ApiPropertyOptional({ description: 'Номер документа. Перед сохранением будет зашифрован.', example: '4512 123456' })
@IsOptional()
@IsString({ message: 'Номер документа должен быть строкой' })
number?: string;
@ApiPropertyOptional({ description: 'Дата выдачи документа в ISO формате', example: '2020-01-10T00:00:00.000Z' })
@IsOptional()
@IsDateString({}, { message: 'Дата выдачи должна быть в ISO формате' })
issuedAt?: string;
@ApiPropertyOptional({ description: 'Дата окончания действия документа в ISO формате', example: '2030-01-10T00:00:00.000Z' })
@IsOptional()
@IsDateString({}, { message: 'Дата окончания должна быть в ISO формате' })
expiresAt?: string;
@ApiPropertyOptional({ description: 'Данные формы документа в JSON. Перед сохранением будут зашифрованы.' })
@IsOptional()
@IsJSON({ message: 'Дополнительные данные должны быть валидным JSON' })
metadataJson?: string;
}
export class UpdateDocumentDto {
@ApiPropertyOptional({ description: 'Номер документа' })
@IsOptional()
@IsString({ message: 'Номер документа должен быть строкой' })
number?: string;
@ApiPropertyOptional({ description: 'Дата выдачи в ISO формате' })
@IsOptional()
@IsDateString({}, { message: 'Дата выдачи должна быть в ISO формате' })
issuedAt?: string;
@ApiPropertyOptional({ description: 'Дата окончания в ISO формате' })
@IsOptional()
@IsDateString({}, { message: 'Дата окончания должна быть в ISO формате' })
expiresAt?: string;
@ApiPropertyOptional({ description: 'Данные формы документа в JSON' })
@IsOptional()
@IsJSON({ message: 'Дополнительные данные должны быть валидным JSON' })
metadataJson?: string;
}

View File

@@ -0,0 +1,140 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsIn, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class OAuthAuthorizeQueryDto {
@ApiProperty({ description: 'ID пользователя, который подтверждает OAuth доступ' })
@IsString({ message: 'ID пользователя должен быть строкой' })
userId!: string;
@ApiProperty({ description: 'OAuth client_id приложения' })
@IsString({ message: 'client_id должен быть строкой' })
clientId!: string;
@ApiProperty({ description: 'Разрешенный redirect_uri приложения' })
@IsString({ message: 'redirect_uri должен быть строкой' })
redirectUri!: string;
@ApiProperty({ description: 'Запрошенные scopes через пробел', example: 'openid profile email' })
@IsString({ message: 'scope должен быть строкой' })
scope!: string;
@ApiPropertyOptional({ description: 'OAuth state для защиты клиента' })
@IsOptional()
@IsString({ message: 'state должен быть строкой' })
state?: string;
}
export class OAuthTokenDto {
@ApiProperty({ description: 'Тип grant', enum: ['authorization_code', 'refresh_token'] })
@IsIn(['authorization_code', 'refresh_token'], { message: 'grant_type должен быть authorization_code или refresh_token' })
grantType!: string;
@ApiPropertyOptional({ description: 'Authorization code' })
@IsOptional()
@IsString({ message: 'code должен быть строкой' })
code?: string;
@ApiPropertyOptional({ description: 'Refresh token' })
@IsOptional()
@IsString({ message: 'refresh_token должен быть строкой' })
refreshToken?: string;
@ApiProperty({ description: 'OAuth client_id' })
@IsString({ message: 'client_id должен быть строкой' })
clientId!: string;
@ApiPropertyOptional({ description: 'OAuth client_secret' })
@IsOptional()
@IsString({ message: 'client_secret должен быть строкой' })
clientSecret?: string;
@ApiPropertyOptional({ description: 'redirect_uri для authorization_code grant' })
@IsOptional()
@IsString({ message: 'redirect_uri должен быть строкой' })
redirectUri?: string;
}
export class SendOtpDto {
@ApiProperty({ description: 'Почта или телефон получателя' })
@IsString({ message: 'Получатель должен быть строкой' })
target!: string;
@ApiProperty({ description: 'Канал доставки', enum: ['email', 'sms'] })
@IsIn(['email', 'sms'], { message: 'Канал должен быть email или sms' })
channel!: string;
@ApiProperty({ description: 'Назначение кода', example: 'login' })
@IsString({ message: 'Назначение должно быть строкой' })
purpose!: string;
@ApiPropertyOptional({ description: 'ID пользователя, если код привязан к аккаунту' })
@IsOptional()
@IsString({ message: 'ID пользователя должен быть строкой' })
userId?: string;
}
export class VerifyOtpDto {
@ApiProperty({ description: 'Почта или телефон получателя' })
@IsString({ message: 'Получатель должен быть строкой' })
target!: string;
@ApiProperty({ description: 'Код подтверждения' })
@IsString({ message: 'Код должен быть строкой' })
code!: string;
@ApiProperty({ description: 'Назначение кода', example: 'login' })
@IsString({ message: 'Назначение должно быть строкой' })
purpose!: string;
}
export class WebAuthnDto {
@ApiProperty({ description: 'ID пользователя' })
@IsString({ message: 'ID пользователя должен быть строкой' })
userId!: string;
}
export class QrSessionDto {
@ApiProperty({ description: 'Название устройства', example: 'Chrome на Windows' })
@IsString({ message: 'Название устройства должно быть строкой' })
deviceName!: string;
}
export class CreateFamilyGroupDto {
@ApiProperty({ description: 'ID владельца семьи' })
@IsString({ message: 'ID владельца должен быть строкой' })
ownerId!: string;
@ApiProperty({ description: 'Название семейной группы', example: 'Семья Мамедовых' })
@IsString({ message: 'Название семьи должно быть строкой' })
@IsNotEmpty({ message: 'Укажите название семьи' })
name!: string;
}
export class UpdateFamilyGroupDto {
@ApiProperty({ description: 'Новое название семейной группы' })
@IsString({ message: 'Название семьи должно быть строкой' })
name!: string;
}
export class AddFamilyMemberDto {
@ApiProperty({ description: 'ID пользователя, которого нужно добавить' })
@IsString({ message: 'ID пользователя должен быть строкой' })
userId!: string;
@ApiProperty({ description: 'Роль участника семьи', example: 'member' })
@IsString({ message: 'Роль должна быть строкой' })
role!: string;
}
export class SendFamilyInviteDto {
@ApiProperty({ description: 'Email, телефон или логин приглашаемого' })
@IsString({ message: 'Укажите контакт приглашаемого' })
@IsNotEmpty({ message: 'Укажите контакт приглашаемого' })
target!: string;
}
export class RespondFamilyInviteDto {
@ApiProperty({ description: 'Принять приглашение' })
@IsBoolean({ message: 'Укажите accept: true или false' })
accept!: boolean;
}

View File

@@ -0,0 +1,40 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsIn, IsOptional, IsString } from 'class-validator';
const IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] as const;
export class AvatarUploadDto {
@ApiProperty({ description: 'MIME-тип изображения', example: 'image/jpeg', enum: IMAGE_TYPES })
@IsString({ message: 'Укажите MIME-тип изображения' })
@IsIn([...IMAGE_TYPES], { message: 'Допустимы только JPEG, PNG, WEBP или GIF' })
contentType!: string;
}
export class ConfirmAvatarDto {
@ApiProperty({ description: 'Ключ объекта в MinIO после загрузки', example: 'avatars/uuid/photo.jpg' })
@IsString({ message: 'Ключ хранилища должен быть строкой' })
storageKey!: string;
}
export class DocumentPhotoUploadDto {
@ApiProperty({ description: 'MIME-тип изображения', example: 'image/jpeg', enum: IMAGE_TYPES })
@IsString({ message: 'Укажите MIME-тип изображения' })
@IsIn([...IMAGE_TYPES], { message: 'Допустимы только JPEG, PNG, WEBP или GIF' })
contentType!: string;
}
export class ChatMediaUploadDto {
@ApiProperty({ description: 'MIME-тип файла', example: 'audio/webm' })
@IsString({ message: 'Укажите MIME-тип файла' })
contentType!: string;
@ApiPropertyOptional({ description: 'Имя файла для определения расширения', example: 'voice.webm' })
@IsOptional()
@IsString({ message: 'Имя файла должно быть строкой' })
fileName?: string;
}
export class UpdateAvatarStorageDto {
@ApiProperty({ description: 'Ключ объекта в MinIO', example: 'avatars/uuid/photo.jpg' })
@IsString({ message: 'Ключ хранилища должен быть строкой' })
storageKey!: string;
}

View File

@@ -0,0 +1 @@
export class MarkNotificationReadDto {}

View File

@@ -0,0 +1,79 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUrl, Matches, Max, Min, MinLength } from 'class-validator';
export class UpdateAvatarDto {
@ApiPropertyOptional({ description: 'Публичная ссылка на аватар (legacy)', example: 'https://cdn.lendry.ru/avatars/user.png' })
@IsOptional()
@IsUrl({}, { message: 'Укажите корректную ссылку на аватар' })
avatarUrl?: string;
@ApiPropertyOptional({ description: 'Ключ объекта в MinIO', example: 'avatars/uuid/photo.jpg' })
@IsOptional()
@IsString({ message: 'Ключ хранилища должен быть строкой' })
avatarStorageKey?: string;
}
export class UpdateProfileDto {
@ApiPropertyOptional({ description: 'Имя пользователя', example: 'Дмитрий' })
@IsOptional()
@IsString({ message: 'Имя должно быть строкой' })
firstName?: string;
@ApiPropertyOptional({ description: 'Фамилия пользователя', example: 'Мамедов' })
@IsOptional()
@IsString({ message: 'Фамилия должна быть строкой' })
lastName?: string;
@ApiPropertyOptional({ description: 'Краткое описание профиля', example: 'Разработчик и владелец аккаунта' })
@IsOptional()
@IsString({ message: 'Описание должно быть строкой' })
bio?: string;
@ApiPropertyOptional({ description: 'Возраст пользователя', minimum: 0, maximum: 130, example: 24 })
@IsOptional()
@IsInt({ message: 'Возраст должен быть целым числом' })
@Min(0, { message: 'Возраст не может быть отрицательным' })
@Max(130, { message: 'Возраст указан некорректно' })
age?: number;
@ApiPropertyOptional({ description: 'Пол пользователя', example: 'male' })
@IsOptional()
@IsString({ message: 'Пол должен быть строкой' })
gender?: string;
}
export class UpdateContactsDto {
@ApiPropertyOptional({ description: 'Основная почта пользователя', example: 'user@example.com' })
@IsOptional()
@IsEmail({}, { message: 'Укажите корректную почту' })
email?: string;
@ApiPropertyOptional({ description: 'Основной телефон пользователя', example: '+79990000000' })
@IsOptional()
@Matches(/^\+?[1-9]\d{7,14}$/, { message: 'Укажите корректный телефон' })
phone?: string;
@ApiPropertyOptional({ description: 'Резервная почта пользователя', example: 'backup@example.com' })
@IsOptional()
@IsEmail({}, { message: 'Укажите корректную резервную почту' })
backupEmail?: string;
@ApiPropertyOptional({ description: 'Резервный телефон пользователя', example: '+79991112233' })
@IsOptional()
@Matches(/^\+?[1-9]\d{7,14}$/, { message: 'Укажите корректный резервный телефон' })
backupPhone?: string;
}
export class SetPasswordDto {
@ApiProperty({ description: 'Новый пароль для входа', minLength: 8 })
@IsString({ message: 'Пароль должен быть строкой' })
@MinLength(8, { message: 'Пароль должен содержать минимум 8 символов' })
password!: string;
}
export class UserIdParamDto {
@ApiProperty({ description: 'ID пользователя' })
@IsString({ message: 'ID пользователя должен быть строкой' })
@IsNotEmpty({ message: 'Передайте ID пользователя' })
userId!: string;
}

View File

@@ -0,0 +1,79 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsArray, IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator';
export enum GatewayOAuthClientType {
CONFIDENTIAL = 'CONFIDENTIAL',
PUBLIC = 'PUBLIC'
}
export class CreateRoleDto {
@ApiProperty({ description: 'Уникальный slug роли', example: 'support' })
@IsString({ message: 'Slug роли должен быть строкой' })
slug!: string;
@ApiProperty({ description: 'Название роли', example: 'Поддержка' })
@IsString({ message: 'Название роли должно быть строкой' })
name!: string;
@ApiPropertyOptional({ description: 'Описание роли' })
@IsOptional()
@IsString({ message: 'Описание должно быть строкой' })
description?: string;
@ApiPropertyOptional({ description: 'Список slug прав', type: [String] })
@IsOptional()
@IsArray({ message: 'Права должны быть массивом' })
@IsString({ each: true, message: 'Каждое право должно быть строкой' })
permissionSlugs?: string[];
}
export class AssignUserRoleDto {
@ApiProperty({ description: 'Slug роли', example: 'admin' })
@IsString({ message: 'Slug роли должен быть строкой' })
roleSlug!: string;
}
export class CreateOAuthClientDto {
@ApiProperty({ description: 'Название приложения', example: 'Lendry Docs' })
@IsString({ message: 'Название должно быть строкой' })
name!: string;
@ApiProperty({ description: 'Redirect URI', type: [String], example: ['https://app.example.com/oauth/callback'] })
@IsArray({ message: 'Redirect URI должны быть массивом' })
@IsString({ each: true, message: 'Каждый redirect URI должен быть строкой' })
redirectUris!: string[];
@ApiProperty({ description: 'Scopes', type: [String], example: ['openid', 'profile', 'email'] })
@IsArray({ message: 'Scopes должны быть массивом' })
@IsString({ each: true, message: 'Каждый scope должен быть строкой' })
scopes!: string[];
@ApiPropertyOptional({ description: 'Тип клиента', enum: GatewayOAuthClientType })
@IsOptional()
@IsEnum(GatewayOAuthClientType, { message: 'Укажите корректный тип клиента' })
type?: GatewayOAuthClientType;
}
export class UpdateOAuthClientDto {
@ApiPropertyOptional({ description: 'Название приложения' })
@IsOptional()
@IsString({ message: 'Название должно быть строкой' })
name?: string;
@ApiPropertyOptional({ description: 'Redirect URI', type: [String] })
@IsOptional()
@IsArray({ message: 'Redirect URI должны быть массивом' })
@IsString({ each: true, message: 'Каждый redirect URI должен быть строкой' })
redirectUris?: string[];
@ApiPropertyOptional({ description: 'Scopes', type: [String] })
@IsOptional()
@IsArray({ message: 'Scopes должны быть массивом' })
@IsString({ each: true, message: 'Каждый scope должен быть строкой' })
scopes?: string[];
@ApiPropertyOptional({ description: 'Активность приложения' })
@IsOptional()
@IsBoolean({ message: 'isActive должно быть boolean' })
isActive?: boolean;
}

View File

@@ -0,0 +1,25 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString, Length, Matches } from 'class-validator';
export class PinDto {
@ApiProperty({ description: 'PIN-код из 4-6 цифр', example: '1234' })
@IsString({ message: 'PIN-код должен быть строкой' })
@Length(4, 6, { message: 'PIN-код должен содержать от 4 до 6 символов' })
@Matches(/^\d+$/, { message: 'PIN-код должен содержать только цифры' })
pin!: string;
}
export class OptionalPinDto {
@ApiPropertyOptional({ description: 'PIN-код для подтверждения действия' })
@IsOptional()
@IsString({ message: 'PIN-код должен быть строкой' })
@Length(4, 6, { message: 'PIN-код должен содержать от 4 до 6 символов' })
@Matches(/^\d+$/, { message: 'PIN-код должен содержать только цифры' })
pin?: string;
}
export class VerifySecurityPinDto extends PinDto {
@ApiProperty({ description: 'ID сессии, которую нужно разблокировать PIN-кодом' })
@IsString({ message: 'ID сессии должен быть строкой' })
sessionId!: string;
}

View File

@@ -0,0 +1,69 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class UpsertSettingDto {
@ApiProperty({ description: 'Ключ настройки', example: 'PIN_LOCK_TIMEOUT_MINUTES' })
@IsString({ message: 'Ключ настройки должен быть строкой' })
@IsNotEmpty({ message: 'Укажите ключ настройки' })
key!: string;
@ApiProperty({ description: 'Значение настройки', example: '15' })
@IsString({ message: 'Значение настройки должно быть строкой' })
value!: string;
@ApiPropertyOptional({ description: 'Описание настройки для администраторов' })
@IsOptional()
@IsString({ message: 'Описание должно быть строкой' })
description?: string;
@ApiPropertyOptional({ description: 'Скрывать ли значение в интерфейсе' })
@IsOptional()
@IsBoolean({ message: 'Признак секрета должен быть логическим значением' })
isSecret?: boolean;
}
export class UpsertSocialProviderDto {
@ApiProperty({ description: 'Название OAuth провайдера', example: 'google' })
@IsString({ message: 'Название провайдера должно быть строкой' })
@IsNotEmpty({ message: 'Укажите провайдера' })
providerName!: string;
@ApiProperty({ description: 'OAuth Client ID провайдера' })
@IsString({ message: 'Client ID должен быть строкой' })
@IsNotEmpty({ message: 'Укажите Client ID' })
clientId!: string;
@ApiPropertyOptional({ description: 'OAuth Client Secret провайдера' })
@IsOptional()
@IsString({ message: 'Client Secret должен быть строкой' })
clientSecret?: string;
@ApiProperty({ description: 'Включен ли провайдер для входа' })
@IsBoolean({ message: 'Статус провайдера должен быть логическим значением' })
isEnabled!: boolean;
}
export class ConnectLinkedAccountDto {
@ApiProperty({ description: 'Название провайдера', example: 'google' })
@IsString({ message: 'Название провайдера должно быть строкой' })
providerName!: string;
@ApiProperty({ description: 'ID пользователя у внешнего провайдера', example: 'google-user-123' })
@IsString({ message: 'ID провайдера должен быть строкой' })
providerId!: string;
@ApiPropertyOptional({ description: 'Почта внешнего аккаунта' })
@IsOptional()
@IsString({ message: 'Почта внешнего аккаунта должна быть строкой' })
email?: string;
@ApiPropertyOptional({ description: 'Отображаемое имя внешнего аккаунта' })
@IsOptional()
@IsString({ message: 'Имя внешнего аккаунта должно быть строкой' })
displayName?: string;
@ApiPropertyOptional({ description: 'Аватар внешнего аккаунта' })
@IsOptional()
@IsString({ message: 'Аватар внешнего аккаунта должен быть строкой' })
avatarUrl?: string;
}