complet auth api
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { Permissions } from '../../common/decorators/permissions.decorator';
|
||||
import { Roles } from '../../common/decorators/roles.decorator';
|
||||
import { PermissionsGuard } from '../../common/guards/permissions.guard';
|
||||
@@ -28,26 +28,36 @@ export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('otp/request')
|
||||
@ApiOperation({ summary: 'Request OTP for mobile auth flow' })
|
||||
@ApiBody({ type: RequestOtpDto })
|
||||
requestOtp(@Body() dto: RequestOtpDto) {
|
||||
return this.authService.requestOtp(dto.phone, dto.fullName);
|
||||
}
|
||||
|
||||
@Post('register/password')
|
||||
@ApiOperation({ summary: 'Register with mobile, username, and password' })
|
||||
@ApiBody({ type: RegisterPasswordDto })
|
||||
registerWithPassword(@Body() dto: RegisterPasswordDto) {
|
||||
return this.authService.registerWithPassword(dto);
|
||||
}
|
||||
|
||||
@Post('login/password')
|
||||
@ApiOperation({ summary: 'Login with username and password' })
|
||||
@ApiBody({ type: LoginPasswordDto })
|
||||
loginWithPassword(@Body() dto: LoginPasswordDto) {
|
||||
return this.authService.loginWithPassword(dto);
|
||||
}
|
||||
|
||||
@Post('otp/verify')
|
||||
@ApiOperation({ summary: 'Verify OTP code for mobile auth flow' })
|
||||
@ApiBody({ type: VerifyOtpDto })
|
||||
verifyOtp(@Body() dto: VerifyOtpDto) {
|
||||
return this.authService.verifyOtp(dto.phone, dto.otp);
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@ApiOperation({ summary: 'Refresh JWT access token' })
|
||||
@ApiBody({ type: RefreshTokenDto })
|
||||
refresh(@Body() dto: RefreshTokenDto) {
|
||||
return this.authService.refreshToken(dto.refreshToken);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class LoginPasswordDto {
|
||||
@ApiProperty({ example: 'alireza' })
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(50)
|
||||
username: string;
|
||||
|
||||
@ApiProperty({ example: 'StrongPass123' })
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
@MaxLength(100)
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class RegisterPasswordDto {
|
||||
@ApiProperty({ example: '989121234567' })
|
||||
@Matches(/^\+?[1-9]\d{7,14}$/)
|
||||
phone: string;
|
||||
|
||||
@ApiProperty({ example: 'alireza' })
|
||||
@IsString()
|
||||
@MinLength(3)
|
||||
@MaxLength(50)
|
||||
username: string;
|
||||
|
||||
@ApiProperty({ example: 'StrongPass123' })
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
@MaxLength(100)
|
||||
password: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'علی رضایی' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class RequestOtpDto {
|
||||
@ApiProperty({ example: '989121234567' })
|
||||
@Matches(/^\+?[1-9]\d{7,14}$/)
|
||||
phone: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'علی رضایی' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, Length, Matches } from 'class-validator';
|
||||
|
||||
export class VerifyOtpDto {
|
||||
@ApiProperty({ example: '989121234567' })
|
||||
@Matches(/^\+?[1-9]\d{7,14}$/)
|
||||
phone: string;
|
||||
|
||||
@ApiProperty({ example: '12345' })
|
||||
@IsString()
|
||||
@Length(4, 6)
|
||||
otp: string;
|
||||
|
||||
72
src/modules/catalog/dto/public-filter-products.dto.ts
Normal file
72
src/modules/catalog/dto/public-filter-products.dto.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { parseJsonValue } from '../../../common/utils/json-transform.util';
|
||||
import { ProductListSort } from '../enums/product-list-sort.enum';
|
||||
|
||||
export class PublicFilterProductsDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
categoryId?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
brandId?: string;
|
||||
|
||||
@ApiPropertyOptional({ type: Object })
|
||||
@IsOptional()
|
||||
@Transform(parseJsonValue)
|
||||
@IsObject()
|
||||
attributes?: Record<string, unknown>;
|
||||
|
||||
@ApiPropertyOptional({ type: [String] })
|
||||
@IsOptional()
|
||||
@Transform(parseJsonValue)
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
tags?: string[];
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) =>
|
||||
typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true',
|
||||
)
|
||||
@IsBoolean()
|
||||
featured?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ enum: ProductListSort })
|
||||
@IsOptional()
|
||||
@IsEnum(ProductListSort)
|
||||
sort?: ProductListSort;
|
||||
|
||||
@ApiPropertyOptional({ default: 1 })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => Number(value))
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ default: 20 })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => Number(value))
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
limit?: number = 20;
|
||||
}
|
||||
@@ -7,12 +7,6 @@ import { ProductsService } from './products.service';
|
||||
|
||||
@ApiTags('Products')
|
||||
@ApiBearerAuth()
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Optional product type filter header. Falls back to query param `type` if omitted.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
@UseGuards(OptionalJwtAuthGuard)
|
||||
@Controller('product')
|
||||
export class ProductDetailsController {
|
||||
@@ -20,6 +14,12 @@ export class ProductDetailsController {
|
||||
|
||||
@Get(':slug')
|
||||
@ApiOperation({ summary: 'Get one published product by slug' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findBySlug(
|
||||
@Param('slug') slug: string,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
|
||||
@@ -21,16 +21,11 @@ import { JwtPayload } from '../auth/interfaces/jwt-payload.interface';
|
||||
import { OptionalJwtAuthGuard } from '../auth/guards/optional-jwt-auth.guard';
|
||||
import { CreateProductReviewDto } from './dto/create-product-review.dto';
|
||||
import { FilterProductsDto } from './dto/filter-products.dto';
|
||||
import { PublicFilterProductsDto } from './dto/public-filter-products.dto';
|
||||
import { ProductsService } from './products.service';
|
||||
|
||||
@ApiTags('Products')
|
||||
@ApiBearerAuth()
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Optional product type filter header. Falls back to query param `type` if omitted.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
@UseGuards(OptionalJwtAuthGuard)
|
||||
@Controller('products')
|
||||
export class ProductsController {
|
||||
@@ -38,25 +33,37 @@ export class ProductsController {
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List published products for storefront' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findAll(
|
||||
@Query() filters: FilterProductsDto,
|
||||
@Query() filters: PublicFilterProductsDto,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
@Req() request?: Request & { user?: JwtPayload | null },
|
||||
) {
|
||||
return this.productsService.findPublic(filters, productType, request?.user);
|
||||
return this.productsService.findPublic(filters as FilterProductsDto, productType, request?.user);
|
||||
}
|
||||
|
||||
@Get('brands/:brandSlug')
|
||||
@ApiOperation({ summary: 'List published products by brand slug' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findByBrandSlug(
|
||||
@Param('brandSlug') brandSlug: string,
|
||||
@Query() filters: FilterProductsDto,
|
||||
@Query() filters: PublicFilterProductsDto,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
@Req() request?: Request & { user?: JwtPayload | null },
|
||||
) {
|
||||
return this.productsService.findPublicByBrandSlug(
|
||||
brandSlug,
|
||||
filters,
|
||||
filters as FilterProductsDto,
|
||||
productType,
|
||||
request?.user,
|
||||
);
|
||||
@@ -64,29 +71,41 @@ export class ProductsController {
|
||||
|
||||
@Get('brands/:brandSlug/filters')
|
||||
@ApiOperation({ summary: 'Get available brand page filters by brand slug' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findBrandFilters(
|
||||
@Param('brandSlug') brandSlug: string,
|
||||
@Query() filters: FilterProductsDto,
|
||||
@Query() filters: PublicFilterProductsDto,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
) {
|
||||
return this.productsService.findPublicBrandFilters(
|
||||
brandSlug,
|
||||
filters,
|
||||
filters as FilterProductsDto,
|
||||
productType,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('categories/:categorySlug')
|
||||
@ApiOperation({ summary: 'List published products by category slug' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findByCategorySlug(
|
||||
@Param('categorySlug') categorySlug: string,
|
||||
@Query() filters: FilterProductsDto,
|
||||
@Query() filters: PublicFilterProductsDto,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
@Req() request?: Request & { user?: JwtPayload | null },
|
||||
) {
|
||||
return this.productsService.findPublicByCategorySlug(
|
||||
categorySlug,
|
||||
filters,
|
||||
filters as FilterProductsDto,
|
||||
productType,
|
||||
request?.user,
|
||||
);
|
||||
@@ -94,14 +113,20 @@ export class ProductsController {
|
||||
|
||||
@Get('categories/:categorySlug/filters')
|
||||
@ApiOperation({ summary: 'Get available category page filters by category slug' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findCategoryFilters(
|
||||
@Param('categorySlug') categorySlug: string,
|
||||
@Query() filters: FilterProductsDto,
|
||||
@Query() filters: PublicFilterProductsDto,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
) {
|
||||
return this.productsService.findPublicCategoryFilters(
|
||||
categorySlug,
|
||||
filters,
|
||||
filters as FilterProductsDto,
|
||||
productType,
|
||||
);
|
||||
}
|
||||
@@ -121,6 +146,12 @@ export class ProductsController {
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get one published product with approved reviews summary' })
|
||||
@ApiHeader({
|
||||
name: 'x-product-type',
|
||||
required: false,
|
||||
description: 'Product type header for public product APIs.',
|
||||
enum: ['Industrial', 'Automotive'],
|
||||
})
|
||||
findOne(
|
||||
@Param('id') id: string,
|
||||
@Headers('x-product-type') productType?: string,
|
||||
|
||||
Reference in New Issue
Block a user