slider module
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-01 14:11:48 +03:30
parent 18f200df9f
commit e491266ea7
12 changed files with 296 additions and 0 deletions
@@ -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;`);
}
}
+2
View File
@@ -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: [],
+4
View File
@@ -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 = 'غذا یافت نشد',
+2
View File
@@ -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);
}
}
+16
View File
@@ -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 {}
+1
View File
@@ -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 ||