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 { InventoryModule } from './modules/inventory/inventory.module';
|
||||
import { IconsModule } from './modules/icons/icons.module';
|
||||
import { SliderModule } from './modules/slider/slider.module';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { cacheConfig } from './config/cache.config';
|
||||
|
||||
@@ -59,6 +60,7 @@ import { cacheConfig } from './config/cache.config';
|
||||
ContactModule,
|
||||
InventoryModule,
|
||||
IconsModule,
|
||||
SliderModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
|
||||
@@ -763,6 +763,10 @@ export const enum DeliveryMessage {
|
||||
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
|
||||
}
|
||||
|
||||
export const enum SliderMessage {
|
||||
NOT_FOUND = 'اسلایدر یافت نشد',
|
||||
}
|
||||
|
||||
export const enum InventoryMessage {
|
||||
AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمیتواند از موجودی کل بیشتر باشد',
|
||||
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
||||
|
||||
@@ -31,6 +31,7 @@ export enum Permission {
|
||||
MANAGE_SCHEDULES = 'manage_schedules',
|
||||
MANAGE_REPORTS = 'manage_reports',
|
||||
MANAGE_CONTACTS = 'manage_contacts',
|
||||
MANAGE_SLIDERS = 'manage_sliders',
|
||||
|
||||
NEW_ORDER_NOTIFICATION='new_order_notification',
|
||||
PAGER_NOTIFICATION='pager_notification'
|
||||
@@ -58,6 +59,7 @@ export const PermissionTitles: Record<Permission, string> = {
|
||||
[Permission.MANAGE_SCHEDULES]: 'مدیریت برنامهها',
|
||||
[Permission.MANAGE_REPORTS]: 'مدیریت گزارشات',
|
||||
[Permission.MANAGE_CONTACTS]: 'مدیریت تماسهای کاربران',
|
||||
[Permission.MANAGE_SLIDERS]: 'مدیریت اسلایدرها',
|
||||
[Permission.NEW_ORDER_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_PAGER ||
|
||||
name === Permission.MANAGE_CONTACTS ||
|
||||
name === Permission.MANAGE_SLIDERS ||
|
||||
name === Permission.MANAGE_ROLES ||
|
||||
|
||||
name === Permission.MANAGE_SETTINGS ||
|
||||
|
||||
Reference in New Issue
Block a user