restauarant list in sup

This commit is contained in:
2025-12-22 16:39:43 +03:30
parent 0970c05b27
commit d1b6c935ad
5 changed files with 172 additions and 21 deletions
+1 -1
View File
@@ -7,6 +7,6 @@
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}
@@ -1,7 +1,8 @@
import { Controller, Get, Post, Body, Patch, Param, UseGuards, Delete } from '@nestjs/common';
import { Controller, Get, Post, Body, Patch, Param, UseGuards, Delete, Query } from '@nestjs/common';
import { RestaurantsService } from '../providers/restaurants.service';
import { CreateRestaurantDto } from '../dto/create-restaurant.dto';
import { UpdateRestaurantDto } from '../dto/update-restaurant.dto';
import { FindRestaurantsDto } from '../dto/find-restaurants.dto';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import {
ApiBearerAuth,
@@ -13,28 +14,24 @@ import {
ApiHeader,
} from '@nestjs/swagger';
import { RestId } from 'src/common/decorators';
import { API_HEADER_SLUG } from 'src/common/constants';
import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard';
@ApiTags('restaurants')
@Controller()
export class RestaurantsController {
constructor(private readonly restaurantsService: RestaurantsService) {}
constructor(private readonly restaurantsService: RestaurantsService) { }
@Get('public/restaurants/:slug')
@ApiOperation({ summary: 'Get restaurant specification by slug' })
@ApiHeader({
name: 'X-Slug',
required: true,
schema: {
type: 'string',
default: 'zhivan',
},
})
@ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'slug', required: true, description: 'Restaurant slug' })
@ApiNotFoundResponse({ description: 'Restaurant not found' })
findBySlug(@Param('slug') slug: string) {
return this.restaurantsService.getRestaurantSpecification(slug);
}
/** Admin Endpoints */
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@Post('admin/restaurants')
@@ -44,13 +41,6 @@ export class RestaurantsController {
return this.restaurantsService.create(createRestaurantDto);
}
// @Get('admin/restaurants')
// @UseGuards(AdminAuthGuard)
// @ApiBearerAuth()
// @ApiOperation({ summary: 'Get all restaurants' })
// findAll() {
// return this.restaurantsService.findAll();
// }
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@@ -86,4 +76,13 @@ export class RestaurantsController {
remove(@Param('id') id: string) {
return this.restaurantsService.remove(id);
}
/** Super Admin Endpoints */
// @UseGuards(SuperAdminAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Get all restaurants with pagination and filters' })
@Get('super-admin/restaurants')
findAll(@Query() dto: FindRestaurantsDto) {
return this.restaurantsService.findAll(dto);
}
}
@@ -0,0 +1,73 @@
import {
IsOptional,
IsString,
IsNumber,
Min,
IsIn,
IsEnum,
IsBoolean,
} from 'class-validator';
import { Type, Transform } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { PlanEnum } from '../interface/plan.interface';
const sortOrderOptions = ['asc', 'desc'] as const;
type SortOrder = (typeof sortOrderOptions)[number];
export class FindRestaurantsDto {
@ApiPropertyOptional({ default: 1, minimum: 1 })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
page: number = 1;
@ApiPropertyOptional({ default: 10, minimum: 1 })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
limit: number = 10;
@ApiPropertyOptional({
description: 'Search by name, slug, domain, or address',
})
@IsOptional()
@IsString()
search?: string;
@ApiPropertyOptional({
description: 'Filter by active status',
type: Boolean,
})
@IsOptional()
@Transform(({ value }) => {
if (value === 'true') return true;
if (value === 'false') return false;
return value;
})
@IsBoolean()
isActive?: boolean;
@ApiPropertyOptional({
enum: PlanEnum,
description: 'Filter by plan type',
})
@IsOptional()
@IsEnum(PlanEnum)
plan?: PlanEnum;
@ApiPropertyOptional({ default: 'createdAt' })
@IsOptional()
@IsString()
orderBy: string = 'createdAt';
@ApiPropertyOptional({
enum: sortOrderOptions,
default: 'desc',
})
@IsOptional()
@IsIn(sortOrderOptions)
order: SortOrder = 'desc';
}
@@ -5,13 +5,15 @@ import { Restaurant } from '../entities/restaurant.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { RestRepository } from '../repositories/rest.repository';
import { RestMessage } from 'src/common/enums/message.enum';
import { FindRestaurantsDto } from '../dto/find-restaurants.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
@Injectable()
export class RestaurantsService {
constructor(
private readonly em: EntityManager,
private readonly restRepository: RestRepository,
) {}
) { }
async create(dto: CreateRestaurantDto): Promise<Restaurant> {
const restaurant = this.em.create(Restaurant, {
@@ -24,8 +26,16 @@ export class RestaurantsService {
return restaurant;
}
findAll() {
return this.restRepository.findAll();
async findAll(dto: FindRestaurantsDto): Promise<PaginatedResult<Restaurant>> {
return this.restRepository.findAllPaginated({
page: dto.page,
limit: dto.limit,
search: dto.search,
isActive: dto.isActive,
plan: dto.plan,
orderBy: dto.orderBy,
order: dto.order,
});
}
async findBySlug(slug: string): Promise<Restaurant> {
@@ -1,10 +1,79 @@
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { FilterQuery } from '@mikro-orm/core';
import { Restaurant } from '../entities/restaurant.entity';
import { Injectable } from '@nestjs/common';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { PlanEnum } from '../interface/plan.interface';
type FindRestaurantsOpts = {
page?: number;
limit?: number;
search?: string;
isActive?: boolean;
plan?: PlanEnum;
orderBy?: string;
order?: 'asc' | 'desc';
};
@Injectable()
export class RestRepository extends EntityRepository<Restaurant> {
constructor(readonly em: EntityManager) {
super(em, Restaurant);
}
/**
* Find restaurants with pagination and optional filters.
* Supports: search (name/slug/domain/address), isActive, plan, ordering.
*/
async findAllPaginated(opts: FindRestaurantsOpts = {}): Promise<PaginatedResult<Restaurant>> {
const {
page = 1,
limit = 10,
search,
isActive,
plan,
orderBy = 'createdAt',
order = 'desc',
} = opts;
const offset = (page - 1) * limit;
const where: FilterQuery<Restaurant> = {};
if (typeof isActive === 'boolean') {
where.isActive = isActive;
}
if (plan) {
where.plan = plan;
}
if (search) {
const pattern = `%${search}%`;
where.$or = [
{ name: { $ilike: pattern } },
{ slug: { $ilike: pattern } },
{ domain: { $ilike: pattern } },
{ address: { $ilike: pattern } },
];
}
const [data, total] = await this.findAndCount(where, {
limit,
offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
});
const totalPages = Math.ceil(total / limit);
return {
data,
meta: {
total,
page,
limit,
totalPages,
},
};
}
}