This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20260701130000_addSlidersTable extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(`
|
||||||
|
create table "sliders" (
|
||||||
|
"id" char(26) not null,
|
||||||
|
"created_at" timestamptz not null default now(),
|
||||||
|
"updated_at" timestamptz not null default now(),
|
||||||
|
"deleted_at" timestamptz null,
|
||||||
|
"restaurant_id" char(26) not null,
|
||||||
|
"image_url" varchar(255) not null,
|
||||||
|
"title" varchar(255) null,
|
||||||
|
"link" varchar(255) null,
|
||||||
|
"order" int not null default 0,
|
||||||
|
"is_active" boolean not null default true,
|
||||||
|
constraint "sliders_pkey" primary key ("id")
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
this.addSql(`create index "sliders_created_at_index" on "sliders" ("created_at");`);
|
||||||
|
this.addSql(`create index "sliders_deleted_at_index" on "sliders" ("deleted_at");`);
|
||||||
|
this.addSql(`create index "sliders_restaurant_id_index" on "sliders" ("restaurant_id");`);
|
||||||
|
this.addSql(`create index "sliders_restaurant_id_is_active_index" on "sliders" ("restaurant_id", "is_active");`);
|
||||||
|
this.addSql(`
|
||||||
|
alter table "sliders"
|
||||||
|
add constraint "sliders_restaurant_id_foreign"
|
||||||
|
foreign key ("restaurant_id") references "restaurants" ("id") on update cascade;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`alter table "sliders" drop constraint if exists "sliders_restaurant_id_foreign";`);
|
||||||
|
this.addSql(`drop table if exists "sliders" cascade;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import { PagerModule } from './modules/pager/pager.module';
|
|||||||
import { ContactModule } from './modules/contact/contact.module';
|
import { ContactModule } from './modules/contact/contact.module';
|
||||||
import { InventoryModule } from './modules/inventory/inventory.module';
|
import { InventoryModule } from './modules/inventory/inventory.module';
|
||||||
import { IconsModule } from './modules/icons/icons.module';
|
import { IconsModule } from './modules/icons/icons.module';
|
||||||
|
import { SliderModule } from './modules/slider/slider.module';
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
import { cacheConfig } from './config/cache.config';
|
import { cacheConfig } from './config/cache.config';
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ import { cacheConfig } from './config/cache.config';
|
|||||||
ContactModule,
|
ContactModule,
|
||||||
InventoryModule,
|
InventoryModule,
|
||||||
IconsModule,
|
IconsModule,
|
||||||
|
SliderModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -763,6 +763,10 @@ export const enum DeliveryMessage {
|
|||||||
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
|
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum SliderMessage {
|
||||||
|
NOT_FOUND = 'اسلایدر یافت نشد',
|
||||||
|
}
|
||||||
|
|
||||||
export const enum InventoryMessage {
|
export const enum InventoryMessage {
|
||||||
AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمیتواند از موجودی کل بیشتر باشد',
|
AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمیتواند از موجودی کل بیشتر باشد',
|
||||||
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export enum Permission {
|
|||||||
MANAGE_SCHEDULES = 'manage_schedules',
|
MANAGE_SCHEDULES = 'manage_schedules',
|
||||||
MANAGE_REPORTS = 'manage_reports',
|
MANAGE_REPORTS = 'manage_reports',
|
||||||
MANAGE_CONTACTS = 'manage_contacts',
|
MANAGE_CONTACTS = 'manage_contacts',
|
||||||
|
MANAGE_SLIDERS = 'manage_sliders',
|
||||||
|
|
||||||
NEW_ORDER_NOTIFICATION='new_order_notification',
|
NEW_ORDER_NOTIFICATION='new_order_notification',
|
||||||
PAGER_NOTIFICATION='pager_notification'
|
PAGER_NOTIFICATION='pager_notification'
|
||||||
@@ -58,6 +59,7 @@ export const PermissionTitles: Record<Permission, string> = {
|
|||||||
[Permission.MANAGE_SCHEDULES]: 'مدیریت برنامهها',
|
[Permission.MANAGE_SCHEDULES]: 'مدیریت برنامهها',
|
||||||
[Permission.MANAGE_REPORTS]: 'مدیریت گزارشات',
|
[Permission.MANAGE_REPORTS]: 'مدیریت گزارشات',
|
||||||
[Permission.MANAGE_CONTACTS]: 'مدیریت تماسهای کاربران',
|
[Permission.MANAGE_CONTACTS]: 'مدیریت تماسهای کاربران',
|
||||||
|
[Permission.MANAGE_SLIDERS]: 'مدیریت اسلایدرها',
|
||||||
[Permission.NEW_ORDER_NOTIFICATION]: "نوتیف سفارش جدید",
|
[Permission.NEW_ORDER_NOTIFICATION]: "نوتیف سفارش جدید",
|
||||||
[Permission.PAGER_NOTIFICATION]: "نوتیف پیجر جدید"
|
[Permission.PAGER_NOTIFICATION]: "نوتیف پیجر جدید"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiParam,
|
||||||
|
ApiBody,
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiHeader,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { SliderService } from '../providers/slider.service';
|
||||||
|
import { CreateSliderDto } from '../dto/create-slider.dto';
|
||||||
|
import { UpdateSliderDto } from '../dto/update-slider.dto';
|
||||||
|
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||||
|
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||||
|
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||||
|
import { Permission } from 'src/common/enums/permission.enum';
|
||||||
|
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||||
|
|
||||||
|
@ApiTags('slider')
|
||||||
|
@Controller()
|
||||||
|
export class SliderController {
|
||||||
|
constructor(private readonly sliderService: SliderService) {}
|
||||||
|
|
||||||
|
@Get('public/sliders/restaurant/:slug')
|
||||||
|
@ApiOperation({ summary: 'Get active sliders by restaurant slug' })
|
||||||
|
@ApiHeader(API_HEADER_SLUG)
|
||||||
|
@ApiParam({ name: 'slug', required: true, description: 'Restaurant slug' })
|
||||||
|
findActiveByRestaurant(@Param('slug') slug: string) {
|
||||||
|
return this.sliderService.findActiveByRestaurantSlug(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Admin ***/
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_SLIDERS)
|
||||||
|
@Post('admin/sliders')
|
||||||
|
@ApiOperation({ summary: 'Create a slider for the restaurant' })
|
||||||
|
@ApiBody({ type: CreateSliderDto })
|
||||||
|
create(@Body() dto: CreateSliderDto, @RestId() restId: string) {
|
||||||
|
return this.sliderService.create(restId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_SLIDERS)
|
||||||
|
@Get('admin/sliders')
|
||||||
|
@ApiOperation({ summary: 'Get all sliders for the restaurant' })
|
||||||
|
findAll(@RestId() restId: string) {
|
||||||
|
return this.sliderService.findByRestaurantId(restId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_SLIDERS)
|
||||||
|
@Get('admin/sliders/:sliderId')
|
||||||
|
@ApiOperation({ summary: 'Get a slider by ID' })
|
||||||
|
@ApiParam({ name: 'sliderId', description: 'Slider ID' })
|
||||||
|
findOne(@Param('sliderId') sliderId: string, @RestId() restId: string) {
|
||||||
|
return this.sliderService.findOneOrFail(restId, sliderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_SLIDERS)
|
||||||
|
@Patch('admin/sliders/:sliderId')
|
||||||
|
@ApiOperation({ summary: 'Update a slider' })
|
||||||
|
@ApiParam({ name: 'sliderId', description: 'Slider ID' })
|
||||||
|
@ApiBody({ type: UpdateSliderDto })
|
||||||
|
update(
|
||||||
|
@Param('sliderId') sliderId: string,
|
||||||
|
@Body() dto: UpdateSliderDto,
|
||||||
|
@RestId() restId: string,
|
||||||
|
) {
|
||||||
|
return this.sliderService.update(restId, sliderId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Permissions(Permission.MANAGE_SLIDERS)
|
||||||
|
@Delete('admin/sliders/:sliderId')
|
||||||
|
@ApiOperation({ summary: 'Delete a slider' })
|
||||||
|
@ApiParam({ name: 'sliderId', description: 'Slider ID' })
|
||||||
|
remove(@Param('sliderId') sliderId: string, @RestId() restId: string) {
|
||||||
|
return this.sliderService.remove(restId, sliderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Min } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class CreateSliderDto {
|
||||||
|
@ApiProperty({ example: 'https://example.com/slider.jpg', description: 'Slider image URL' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
imageUrl!: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'پیشنهاد ویژه', description: 'Slider title' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'https://example.com/promo', description: 'Optional link when slider is clicked' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
link?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 0, description: 'Display order for sorting sliders' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Min(0)
|
||||||
|
@Type(() => Number)
|
||||||
|
order?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: true, description: 'Whether the slider is active' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@Type(() => Boolean)
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateSliderDto } from './create-slider.dto';
|
||||||
|
|
||||||
|
export class UpdateSliderDto extends PartialType(CreateSliderDto) {}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core';
|
||||||
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
|
|
||||||
|
@Entity({ tableName: 'sliders' })
|
||||||
|
@Index({ properties: ['restaurant', 'isActive'] })
|
||||||
|
@Index({ properties: ['restaurant'] })
|
||||||
|
export class Slider extends BaseEntity {
|
||||||
|
@ManyToOne(() => Restaurant)
|
||||||
|
restaurant!: Restaurant;
|
||||||
|
|
||||||
|
@Property()
|
||||||
|
imageUrl!: string;
|
||||||
|
|
||||||
|
@Property({ nullable: true })
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@Property({ nullable: true })
|
||||||
|
link?: string;
|
||||||
|
|
||||||
|
@Property({ type: 'integer', default: 0 })
|
||||||
|
order: number = 0;
|
||||||
|
|
||||||
|
@Property({ default: true })
|
||||||
|
isActive: boolean = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
||||||
|
import { Slider } from '../entities/slider.entity';
|
||||||
|
import { SliderRepository } from '../repositories/slider.repository';
|
||||||
|
import { CreateSliderDto } from '../dto/create-slider.dto';
|
||||||
|
import { UpdateSliderDto } from '../dto/update-slider.dto';
|
||||||
|
import { SliderMessage } from 'src/common/enums/message.enum';
|
||||||
|
import { RestaurantsService } from 'src/modules/restaurants/providers/restaurants.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SliderService {
|
||||||
|
constructor(
|
||||||
|
private readonly sliderRepository: SliderRepository,
|
||||||
|
private readonly restService: RestaurantsService,
|
||||||
|
private readonly em: EntityManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(restId: string, dto: CreateSliderDto): Promise<Slider> {
|
||||||
|
const restaurant = await this.restService.findOneOrFail(restId);
|
||||||
|
|
||||||
|
const data: RequiredEntityData<Slider> = {
|
||||||
|
restaurant,
|
||||||
|
imageUrl: dto.imageUrl,
|
||||||
|
title: dto.title,
|
||||||
|
link: dto.link,
|
||||||
|
order: dto.order ?? 0,
|
||||||
|
isActive: dto.isActive ?? true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const slider = this.sliderRepository.create(data);
|
||||||
|
await this.em.persistAndFlush(slider);
|
||||||
|
return slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByRestaurantId(restId: string): Promise<Slider[]> {
|
||||||
|
return this.sliderRepository.find(
|
||||||
|
{ restaurant: { id: restId } },
|
||||||
|
{ orderBy: { order: 'asc', createdAt: 'asc' } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findActiveByRestaurantSlug(slug: string): Promise<Slider[]> {
|
||||||
|
return this.sliderRepository.find(
|
||||||
|
{ restaurant: { slug, isActive: true }, isActive: true },
|
||||||
|
{ orderBy: { order: 'asc', createdAt: 'asc' } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneOrFail(restId: string, sliderId: string): Promise<Slider> {
|
||||||
|
const slider = await this.sliderRepository.findOne({
|
||||||
|
id: sliderId,
|
||||||
|
restaurant: { id: restId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!slider) {
|
||||||
|
throw new NotFoundException(SliderMessage.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(restId: string, sliderId: string, dto: UpdateSliderDto): Promise<Slider> {
|
||||||
|
const slider = await this.findOneOrFail(restId, sliderId);
|
||||||
|
this.em.assign(slider, dto);
|
||||||
|
await this.em.persistAndFlush(slider);
|
||||||
|
return slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(restId: string, sliderId: string): Promise<void> {
|
||||||
|
const slider = await this.findOneOrFail(restId, sliderId);
|
||||||
|
slider.deletedAt = new Date();
|
||||||
|
await this.em.persistAndFlush(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||||
|
import { Slider } from '../entities/slider.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SliderRepository extends EntityRepository<Slider> {
|
||||||
|
constructor(readonly em: EntityManager) {
|
||||||
|
super(em, Slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { Slider } from './entities/slider.entity';
|
||||||
|
import { SliderController } from './controllers/slider.controller';
|
||||||
|
import { SliderService } from './providers/slider.service';
|
||||||
|
import { SliderRepository } from './repositories/slider.repository';
|
||||||
|
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [MikroOrmModule.forFeature([Slider]), JwtModule, RestaurantsModule],
|
||||||
|
controllers: [SliderController],
|
||||||
|
providers: [SliderService, SliderRepository],
|
||||||
|
exports: [SliderService, SliderRepository],
|
||||||
|
})
|
||||||
|
export class SliderModule {}
|
||||||
@@ -16,6 +16,7 @@ export const rolesData: RoleConfig[] = [
|
|||||||
name === Permission.MANAGE_SCHEDULES ||
|
name === Permission.MANAGE_SCHEDULES ||
|
||||||
name === Permission.MANAGE_PAGER ||
|
name === Permission.MANAGE_PAGER ||
|
||||||
name === Permission.MANAGE_CONTACTS ||
|
name === Permission.MANAGE_CONTACTS ||
|
||||||
|
name === Permission.MANAGE_SLIDERS ||
|
||||||
name === Permission.MANAGE_ROLES ||
|
name === Permission.MANAGE_ROLES ||
|
||||||
|
|
||||||
name === Permission.MANAGE_SETTINGS ||
|
name === Permission.MANAGE_SETTINGS ||
|
||||||
|
|||||||
Reference in New Issue
Block a user