add recorator
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
export { UserId } from './user-id.decorator';
|
||||||
|
export { RestId } from './rest-id.decorator';
|
||||||
|
export { RateLimit } from './rate-limit.decorator';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
|
||||||
|
import type { Request } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator to extract restId from the authenticated request.
|
||||||
|
* Must be used after AdminAuthGuard or AuthGuard that sets request.restId.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* @Get('/restaurants')
|
||||||
|
* @UseGuards(AdminAuthGuard)
|
||||||
|
* getRestaurants(@RestId() restId: string) {
|
||||||
|
* return this.restaurantService.findById(restId);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const RestId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
|
||||||
|
const request = ctx.switchToHttp().getRequest<Request & { restId?: string }>();
|
||||||
|
return request.restId || '';
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
|
||||||
|
import type { Request } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator to extract userId from the authenticated request.
|
||||||
|
* Must be used after AdminAuthGuard or AuthGuard that sets request.userId.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* @Get('/profile')
|
||||||
|
* @UseGuards(AdminAuthGuard)
|
||||||
|
* getProfile(@UserId() userId: string) {
|
||||||
|
* return this.userService.findById(userId);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const UserId = createParamDecorator((data: unknown, ctx: ExecutionContext): string => {
|
||||||
|
const request = ctx.switchToHttp().getRequest<Request & { userId?: string }>();
|
||||||
|
return request.userId || '';
|
||||||
|
});
|
||||||
@@ -10,7 +10,7 @@ export interface AuthRequest extends Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuard implements CanActivate {
|
export class AdminAuthGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
@@ -30,7 +30,6 @@ export class AuthGuard implements CanActivate {
|
|||||||
const payload = await this.jwtService.verifyAsync<ITokenPayload>(token, {
|
const payload = await this.jwtService.verifyAsync<ITokenPayload>(token, {
|
||||||
secret,
|
secret,
|
||||||
});
|
});
|
||||||
console.log('payload', payload);
|
|
||||||
request['userId'] = payload.userId;
|
request['userId'] = payload.userId;
|
||||||
request['restId'] = payload.restId;
|
request['restId'] = payload.restId;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsNumber, IsBoolean, IsOptional, IsString, Min, Max } from 'class-validator';
|
import { IsNumber, IsBoolean, IsOptional, IsString, Min, Max, Matches } from 'class-validator';
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
@@ -10,12 +10,18 @@ export class CreateScheduleDto {
|
|||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
weekDay!: number;
|
weekDay!: number;
|
||||||
|
|
||||||
@ApiProperty({ example: '08:00', description: 'زمان باز شدن (دقیقه از نیمه شب)' })
|
@ApiProperty({ example: '08:00', description: 'زمان باز شدن (HH:MM, از ۰۰:۰۰ تا ۲۳:۵۹)' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, {
|
||||||
|
message: 'openTime باید در فرمت HH:MM باشد (00:00 تا 23:59)',
|
||||||
|
})
|
||||||
openTime!: string;
|
openTime!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '23:00', description: 'زمان بستن (دقیقه از نیمه شب)' })
|
@ApiProperty({ example: '23:00', description: 'زمان بستن (HH:MM, از ۰۰:۰۰ تا ۲۳:۵۹)' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/, {
|
||||||
|
message: 'closeTime باید در فرمت HH:MM باشد (00:00 تا 23:59)',
|
||||||
|
})
|
||||||
closeTime!: string;
|
closeTime!: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ example: true, description: 'آیا این برنامه فعال است؟' })
|
@ApiPropertyOptional({ example: true, description: 'آیا این برنامه فعال است؟' })
|
||||||
@@ -23,4 +29,8 @@ export class CreateScheduleDto {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@Type(() => Boolean)
|
@Type(() => Boolean)
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'rest-ulid-123', description: 'شناسه رستوران' })
|
||||||
|
@IsString()
|
||||||
|
restId!: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Controller, Get, UseGuards, Query, ValidationPipe } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth, ApiOperation, ApiOkResponse } from '@nestjs/swagger';
|
||||||
|
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { FindUsersDto } from './dto/find-user.dto';
|
||||||
|
import { UserId } from 'src/common/decorators/user-id.decorator';
|
||||||
|
import { RestId } from 'src/common/decorators';
|
||||||
|
|
||||||
|
@ApiTags('Admin/User')
|
||||||
|
@Controller('admin/user')
|
||||||
|
export class AdminUserController {
|
||||||
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Get the current authenticated user profile' })
|
||||||
|
@Get('/me')
|
||||||
|
async getUser(@UserId() userId: string) {
|
||||||
|
const user = await this.userService.findById(userId);
|
||||||
|
return {
|
||||||
|
message: `GET request: Retrieving profile for authenticated user`,
|
||||||
|
user,
|
||||||
|
userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Get paginated list of users with filters' })
|
||||||
|
@ApiOkResponse({ description: 'Paginated list of users' })
|
||||||
|
@Get('/')
|
||||||
|
async findAll(
|
||||||
|
@Query(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
query: FindUsersDto,
|
||||||
|
@RestId() restId: string,
|
||||||
|
) {
|
||||||
|
return this.userService.findAll(restId, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ export class FindUsersDto {
|
|||||||
* @default 1
|
* @default 1
|
||||||
*/
|
*/
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'The page number to retrieve.',
|
description: 'شماره صفحه',
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
minimum: 1,
|
minimum: 1,
|
||||||
@@ -29,7 +29,7 @@ export class FindUsersDto {
|
|||||||
* @default 10
|
* @default 10
|
||||||
*/
|
*/
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'The number of items per page.',
|
description: 'تعداد مورد در هر صفحه',
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 10,
|
default: 10,
|
||||||
minimum: 1,
|
minimum: 1,
|
||||||
@@ -45,9 +45,8 @@ export class FindUsersDto {
|
|||||||
* Searches firstName, lastName, phone, and nationalCode.
|
* Searches firstName, lastName, phone, and nationalCode.
|
||||||
*/
|
*/
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'A general search term (firstName, lastName, phone, nationalCode).',
|
description: 'جستجو بر اساس نام، نامخانوادگی یا شماره تماس',
|
||||||
type: String,
|
type: String,
|
||||||
example: 'John Doe',
|
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -58,12 +57,9 @@ export class FindUsersDto {
|
|||||||
* @default "createdAt"
|
* @default "createdAt"
|
||||||
*/
|
*/
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'The field to sort the results by.',
|
description: 'فیلد مرتبسازی',
|
||||||
type: String,
|
type: String,
|
||||||
default: 'createdAt',
|
default: 'createdAt',
|
||||||
// You can provide a list of valid keys for better documentation
|
|
||||||
// You must ensure 'keyof User' is correctly exported/available here
|
|
||||||
example: 'lastName',
|
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -74,8 +70,8 @@ export class FindUsersDto {
|
|||||||
* @default "desc"
|
* @default "desc"
|
||||||
*/
|
*/
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: 'The direction to sort the results.',
|
description: 'جهت مرتبسازی (asc یا desc)',
|
||||||
enum: sortOrderOptions, // Use the constant array
|
enum: sortOrderOptions,
|
||||||
default: 'desc',
|
default: 'desc',
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { Entity, Property, OneToMany, Collection } from '@mikro-orm/core';
|
import { Entity, Property, OneToMany, Collection, OneToOne } from '@mikro-orm/core';
|
||||||
import { RefreshToken } from './refresh-token.entity';
|
import { RefreshToken } from './refresh-token.entity';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
|
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'users' })
|
@Entity({ tableName: 'users' })
|
||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
|
@OneToOne(() => Restaurant, { owner: true })
|
||||||
|
restaurant!: Restaurant;
|
||||||
|
|
||||||
|
@OneToMany(() => RefreshToken, token => token.user)
|
||||||
|
refreshTokens = new Collection<RefreshToken>(this);
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
firstName!: string;
|
firstName!: string;
|
||||||
|
|
||||||
@@ -33,7 +40,4 @@ export class User extends BaseEntity {
|
|||||||
|
|
||||||
@Property({ default: 0, type: 'int' })
|
@Property({ default: 0, type: 'int' })
|
||||||
points: number = 0;
|
points: number = 0;
|
||||||
|
|
||||||
@OneToMany(() => RefreshToken, token => token.user)
|
|
||||||
refreshTokens = new Collection<RefreshToken>(this);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,36 @@
|
|||||||
import { Controller, Get, Req, UseGuards, Patch, Body, ValidationPipe } from '@nestjs/common';
|
import { Controller, Get, UseGuards, Patch, Body, ValidationPipe } from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||||
import { AuthGuard, type AuthRequest } from 'src/modules/auth/guards/auth.guard';
|
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
import { UserId } from 'src/common/decorators/user-id.decorator';
|
||||||
|
|
||||||
@ApiTags('User')
|
@ApiTags('User')
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: 'Get the current authenticated user profile' })
|
@ApiOperation({ summary: 'Get the current authenticated user profile' })
|
||||||
@Get('/me')
|
@Get('/me')
|
||||||
async getUser(@Req() req: AuthRequest) {
|
async getUser(@UserId() userId: string) {
|
||||||
const userId = req.userId;
|
|
||||||
const restId = req.restId;
|
|
||||||
const user = await this.userService.findById(userId);
|
const user = await this.userService.findById(userId);
|
||||||
return {
|
return {
|
||||||
message: `GET request: Retrieving profile for authenticated user `,
|
message: `GET request: Retrieving profile for authenticated user`,
|
||||||
user,
|
user,
|
||||||
userId,
|
userId,
|
||||||
restId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 2. Update User Specification (PATCH /user)
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: 'Update the authenticated user profile' })
|
@ApiOperation({ summary: 'Update the authenticated user profile' })
|
||||||
@Patch('/')
|
@Patch('/')
|
||||||
async updateUser(
|
async updateUser(
|
||||||
@Req() req: AuthRequest,
|
@UserId() userId: string,
|
||||||
@Body(new ValidationPipe({ transform: true, whitelist: true })) dto: UpdateUserDto,
|
@Body(new ValidationPipe({ transform: true, whitelist: true })) dto: UpdateUserDto,
|
||||||
) {
|
) {
|
||||||
const userId = req.userId;
|
|
||||||
const user = await this.userService.updateUser(userId, dto);
|
const user = await this.userService.updateUser(userId, dto);
|
||||||
return {
|
return {
|
||||||
message: `PATCH request: Updating profile for user ${userId}`,
|
message: `PATCH request: Updating profile for user ${userId}`,
|
||||||
@@ -41,16 +38,4 @@ export class UserController {
|
|||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// @UseGuards(AdminAuthGuard)
|
|
||||||
// @ApiBearerAuth()
|
|
||||||
// @ApiOperation({ summary: 'Users : Admin Route' })
|
|
||||||
// @Get('/admin/users')
|
|
||||||
// async findAll(
|
|
||||||
// // Use ValidationPipe to validate and transform the DTO
|
|
||||||
// @Query(new ValidationPipe({ transform: true, whitelist: true }))
|
|
||||||
// query: FindUsersDto,
|
|
||||||
// ) {
|
|
||||||
// return this.userService.findAll(query);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { UserController } from './user.controller';
|
import { AdminUserController } from './adminUser.controller';
|
||||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
@@ -8,7 +8,7 @@ import { UserRepository } from './repositories/user.repository';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UserService, UserRepository],
|
providers: [UserService, UserRepository],
|
||||||
controllers: [UserController],
|
controllers: [AdminUserController],
|
||||||
imports: [MikroOrmModule.forFeature([User]), JwtModule],
|
imports: [MikroOrmModule.forFeature([User]), JwtModule],
|
||||||
exports: [UserService, UserRepository],
|
exports: [UserService, UserRepository],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -96,14 +96,14 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(dto: FindUsersDto): Promise<PaginatedResult<User>> {
|
async findAll(restId: string, dto: FindUsersDto): Promise<PaginatedResult<User>> {
|
||||||
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto;
|
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto;
|
||||||
|
|
||||||
// 1. Calculate pagination
|
// 1. Calculate pagination
|
||||||
const offset = (page - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
// 2. Build the 'where' filter query
|
// 2. Build the 'where' filter query
|
||||||
const where: FilterQuery<User> = {};
|
const where: FilterQuery<User> = { restaurant: { id: restId } };
|
||||||
|
|
||||||
// 4. Add 'search' logic (case-insensitive)
|
// 4. Add 'search' logic (case-insensitive)
|
||||||
if (search) {
|
if (search) {
|
||||||
|
|||||||
Reference in New Issue
Block a user