refactor food

This commit is contained in:
2025-12-17 09:06:03 +03:30
parent 1b42a0b4b4
commit 87534e8c6f
4 changed files with 34 additions and 23 deletions
@@ -21,7 +21,7 @@ import { RestId } from 'src/common/decorators';
@ApiTags('foods') @ApiTags('foods')
@Controller() @Controller()
export class FoodController { export class FoodController {
constructor(private readonly foodsService: FoodService) {} constructor(private readonly foodsService: FoodService) { }
@Get('public/foods/restaurant/:slug') @Get('public/foods/restaurant/:slug')
@ApiOperation({ summary: 'Get all foods by restaurant slug' }) @ApiOperation({ summary: 'Get all foods by restaurant slug' })
@@ -45,7 +45,7 @@ export class FoodController {
@ApiOkResponse({ description: 'The food', type: CreateFoodDto }) @ApiOkResponse({ description: 'The food', type: CreateFoodDto })
@ApiNotFoundResponse({ description: 'Food not found' }) @ApiNotFoundResponse({ description: 'Food not found' })
findPublicFoodById(@Param('foodId') foodId: string) { findPublicFoodById(@Param('foodId') foodId: string) {
return this.foodsService.findById(foodId); return this.foodsService.findPublicById(foodId);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -81,8 +81,8 @@ export class FoodController {
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
@ApiOkResponse({ description: 'The food', type: CreateFoodDto }) @ApiOkResponse({ description: 'The food', type: CreateFoodDto })
@ApiNotFoundResponse({ description: 'Food not found' }) @ApiNotFoundResponse({ description: 'Food not found' })
findById(@Param('id') id: string) { findById(@Param('id') id: string, @RestId() restId: string) {
return this.foodsService.findById(id); return this.foodsService.findAdminById(restId, id);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -92,15 +92,15 @@ export class FoodController {
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
@ApiBody({ type: UpdateFoodDto }) @ApiBody({ type: UpdateFoodDto })
@ApiOkResponse({ description: 'The updated food', type: UpdateFoodDto }) @ApiOkResponse({ description: 'The updated food', type: UpdateFoodDto })
update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto) { update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @RestId() restId: string) {
return this.foodsService.update(id, updateFoodDto); return this.foodsService.update(restId, id, updateFoodDto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Delete('admin/foods/:id') @Delete('admin/foods/:id')
@ApiOperation({ summary: 'Delete (soft) a food' }) @ApiOperation({ summary: 'Delete (soft) a food' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
remove(@Param('id') id: string) { remove(@Param('id') id: string, @RestId() restId: string) {
return this.foodsService.remove(id); return this.foodsService.remove(restId, id);
} }
} }
@@ -13,7 +13,7 @@ export class CategoryService {
constructor( constructor(
private readonly categoryRepository: CategoryRepository, private readonly categoryRepository: CategoryRepository,
private readonly em: EntityManager, private readonly em: EntityManager,
) {} ) { }
async create(restId: string, dto: CreateCategoryDto): Promise<Category> { async create(restId: string, dto: CreateCategoryDto): Promise<Category> {
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const restaurant = await this.em.findOne(Restaurant, { id: restId });
@@ -37,7 +37,7 @@ export class CategoryService {
if (!restaurant || !restaurant.id) { if (!restaurant || !restaurant.id) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
return this.categoryRepository.find({ restaurant: restaurant }); return this.categoryRepository.find({ restaurant: restaurant, isActive: true });
} }
async findAllByRestaurantId(restId: string): Promise<Category[]> { async findAllByRestaurantId(restId: string): Promise<Category[]> {
+22 -11
View File
@@ -29,7 +29,7 @@ export class FoodService {
if (!restaurant) { if (!restaurant) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
const category = await this.categoryRepository.findOne({ id: categoryId }); const category = await this.categoryRepository.findOne({ id: categoryId, restaurant: { id: restId } });
if (!category) { if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND); throw new NotFoundException(CategoryMessage.NOT_FOUND);
} }
@@ -74,11 +74,21 @@ export class FoodService {
return this.foodRepository.findAllPaginated(restId, dto); return this.foodRepository.findAllPaginated(restId, dto);
} }
async findById(id: string): Promise<Food> { /**
const food = await this.foodRepository.findOne({ id }, { populate: ['category'] }); * Public food detail (only active foods are visible).
if (!food) { */
throw new NotFoundException(FoodMessage.NOT_FOUND); async findPublicById(id: string): Promise<Food> {
} const food = await this.foodRepository.findOne({ id, isActive: true }, { populate: ['category'] });
if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
return food;
}
/**
* Admin food detail (scoped to the authenticated restaurant).
*/
async findAdminById(restId: string, id: string): Promise<Food> {
const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['category'] });
if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
return food; return food;
} }
@@ -112,14 +122,14 @@ export class FoodService {
return foods; return foods;
} }
async update(id: string, dto: Partial<CreateFoodDto>): Promise<Food> { async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
const food = await this.foodRepository.findOne({ id }, { populate: ['restaurant'] }); const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
if (!food) { if (!food) {
throw new NotFoundException(FoodMessage.NOT_FOUND); throw new NotFoundException(FoodMessage.NOT_FOUND);
} }
// attach new categories if provided (adds, does not attempt to preserve/remove existing ones) // attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
if (dto.categoryId) { if (dto.categoryId) {
const category = await this.categoryRepository.findOne({ id: dto.categoryId }); const category = await this.categoryRepository.findOne({ id: dto.categoryId, restaurant: { id: restId } });
if (!category) { if (!category) {
throw new NotFoundException(CategoryMessage.NOT_FOUND); throw new NotFoundException(CategoryMessage.NOT_FOUND);
@@ -130,6 +140,7 @@ export class FoodService {
// assign other fields from DTO // assign other fields from DTO
const { categoryId, ...rest } = dto; const { categoryId, ...rest } = dto;
void categoryId;
this.em.assign(food, rest); this.em.assign(food, rest);
await this.em.persistAndFlush(food); await this.em.persistAndFlush(food);
@@ -140,8 +151,8 @@ export class FoodService {
return food; return food;
} }
async remove(id: string) { async remove(restId: string, id: string): Promise<void> {
const food = await this.foodRepository.findOne({ id }, { populate: ['restaurant'] }); const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
if (!food) { if (!food) {
throw new NotFoundException(FoodMessage.NOT_FOUND); throw new NotFoundException(FoodMessage.NOT_FOUND);
} }
@@ -40,8 +40,8 @@ export class FoodRepository extends EntityRepository<Food> {
} }
if (categoryId) { if (categoryId) {
// filter by related categories (typed via FilterQuery) // filter by related category (Food has a single `category` relation)
Object.assign(where, { categories: { id: categoryId } } as unknown as FilterQuery<Food>); Object.assign(where, { category: { id: categoryId } } as unknown as FilterQuery<Food>);
} }
const [data, total] = await this.findAndCount(where, { const [data, total] = await this.findAndCount(where, {