This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260517132951_AddConfigModule extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`create table "configs" ("id" char(26) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, "key" varchar(255) not null, "value" varchar(255) not null, "shop_id" char(26) null, constraint "configs_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "configs_created_at_index" on "configs" ("created_at");`);
|
||||
this.addSql(`create index "configs_deleted_at_index" on "configs" ("deleted_at");`);
|
||||
this.addSql(`alter table "configs" add constraint "configs_shop_id_key_unique" unique ("shop_id", "key");`);
|
||||
|
||||
this.addSql(`alter table "configs" add constraint "configs_shop_id_foreign" foreign key ("shop_id") references "shops" ("id") on update cascade on delete set null;`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
}
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { Module } from '@nestjs/common';
|
||||
import dataBaseConfig from './config/mikro-orm.config';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ConfigModule as NestConfigModule } from '@nestjs/config';
|
||||
import { UserModule } from './modules/users/user.module';
|
||||
import { UtilsModule } from './modules/utils/utils.module';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
@@ -24,10 +24,11 @@ import { ContactModule } from './modules/contact/contact.module';
|
||||
import { IconsModule } from './modules/icons/icons.module';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { cacheConfig } from './config/cache.config';
|
||||
import { ShopConfigModule } from './modules/config/config.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
||||
NestConfigModule.forRoot({ isGlobal: true, cache: true }),
|
||||
CacheModule.registerAsync(cacheConfig()),
|
||||
MikroOrmModule.forRootAsync(dataBaseConfig),
|
||||
UserModule,
|
||||
@@ -55,6 +56,7 @@ import { cacheConfig } from './config/cache.config';
|
||||
EventEmitterModule.forRoot(),
|
||||
ContactModule,
|
||||
IconsModule,
|
||||
ShopConfigModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { ConfigService } from './providers/config.service';
|
||||
import { ConfigController } from './controllers/config.controller';
|
||||
import { Config } from './entities/config.entity';
|
||||
import { ConfigRepository } from './repositories/config.repository';
|
||||
import { ShopsModule } from '../shops/shops.module';
|
||||
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Config]), JwtModule, ShopsModule],
|
||||
controllers: [ConfigController],
|
||||
providers: [ConfigService, ConfigRepository],
|
||||
exports: [ConfigService, ConfigRepository],
|
||||
})
|
||||
export class ShopConfigModule {}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiHeader,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { ConfigService } from '../providers/config.service';
|
||||
import { CreateConfigDto } from '../dto/create-config.dto';
|
||||
import { UpdateConfigDto } from '../dto/update-config.dto';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { ShopId } from 'src/common/decorators/shop-id.decorator';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Config } from '../entities/config.entity';
|
||||
|
||||
@ApiTags('config')
|
||||
@Controller()
|
||||
export class ConfigController {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
@Get('public/config/shop/:slug')
|
||||
@ApiOperation({ summary: 'Get shop configs by slug (public)' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiParam({ name: 'slug', description: 'Shop slug' })
|
||||
findAllPublic(@Param('slug') slug: string): Promise<Config[]> {
|
||||
return this.configService.findAllBySlug(slug);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_SETTINGS)
|
||||
@Post('admin/config')
|
||||
@ApiOperation({ summary: 'Create a shop config (admin)' })
|
||||
@ApiBody({ type: CreateConfigDto })
|
||||
create(@Body() dto: CreateConfigDto, @ShopId() shopId: string): Promise<Config> {
|
||||
return this.configService.create(shopId, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_SETTINGS)
|
||||
@Get('admin/config')
|
||||
@ApiOperation({ summary: 'Get all shop configs (admin)' })
|
||||
findAll(@ShopId() shopId: string): Promise<Config[]> {
|
||||
return this.configService.findAllByShopId(shopId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_SETTINGS)
|
||||
@Get('admin/config/:id')
|
||||
@ApiOperation({ summary: 'Get a shop config by ID (admin)' })
|
||||
@ApiParam({ name: 'id', description: 'Config ID' })
|
||||
findOne(@ShopId() shopId: string, @Param('id') id: string): Promise<Config> {
|
||||
return this.configService.findOne(shopId, id);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_SETTINGS)
|
||||
@Patch('admin/config/:id')
|
||||
@ApiOperation({ summary: 'Update a shop config (admin)' })
|
||||
@ApiParam({ name: 'id', description: 'Config ID' })
|
||||
@ApiBody({ type: UpdateConfigDto })
|
||||
update(
|
||||
@ShopId() shopId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateConfigDto,
|
||||
): Promise<Config> {
|
||||
return this.configService.update(shopId, id, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_SETTINGS)
|
||||
@Delete('admin/config/:id')
|
||||
@ApiOperation({ summary: 'Delete a shop config (admin)' })
|
||||
@ApiParam({ name: 'id', description: 'Config ID' })
|
||||
async remove(@ShopId() shopId: string, @Param('id') id: string): Promise<void> {
|
||||
await this.configService.remove(shopId, id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class CreateConfigDto {
|
||||
@ApiProperty({ example: 'theme_color', description: 'Config key' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
key!: string;
|
||||
|
||||
@ApiProperty({ example: '#ff5500', description: 'Config value' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
value!: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateConfigDto } from './create-config.dto';
|
||||
|
||||
export class UpdateConfigDto extends PartialType(CreateConfigDto) {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Entity, ManyToOne, Property, Unique } from '@mikro-orm/core';
|
||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||
import { Shop } from '../../shops/entities/shop.entity';
|
||||
|
||||
@Entity({ tableName: 'configs' })
|
||||
@Unique({ properties: ['shop', 'key'] })
|
||||
export class Config extends BaseEntity {
|
||||
@Property()
|
||||
key!: string;
|
||||
|
||||
@Property()
|
||||
value!: string;
|
||||
|
||||
@ManyToOne(() => Shop, { nullable: true })
|
||||
shop?: Shop;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
||||
import { CreateConfigDto } from '../dto/create-config.dto';
|
||||
import { UpdateConfigDto } from '../dto/update-config.dto';
|
||||
import { Config } from '../entities/config.entity';
|
||||
import { ConfigRepository } from '../repositories/config.repository';
|
||||
import { ShopRepository } from '../../shops/repositories/rest.repository';
|
||||
import { ShopMessage } from 'src/common/enums/message.enum';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
constructor(
|
||||
private readonly configRepository: ConfigRepository,
|
||||
private readonly shopRepository: ShopRepository,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(shopId: string, dto: CreateConfigDto): Promise<Config> {
|
||||
const shop = await this.shopRepository.findOne({ id: shopId });
|
||||
if (!shop) {
|
||||
throw new NotFoundException(ShopMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
const existing = await this.configRepository.findOne({
|
||||
shop: { id: shopId },
|
||||
key: dto.key,
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('این کلید تنظیمات قبلاً برای این فروشگاه ثبت شده است.');
|
||||
}
|
||||
|
||||
const data: RequiredEntityData<Config> = {
|
||||
shop,
|
||||
key: dto.key,
|
||||
value: dto.value,
|
||||
};
|
||||
|
||||
const config = this.configRepository.create(data);
|
||||
await this.em.persistAndFlush(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
async findAllByShopId(shopId: string): Promise<Config[]> {
|
||||
return this.configRepository.findAllByShopId(shopId);
|
||||
}
|
||||
|
||||
async findAllBySlug(slug: string): Promise<Config[]> {
|
||||
const shop = await this.shopRepository.findOne({ slug });
|
||||
if (!shop) {
|
||||
throw new NotFoundException(ShopMessage.NOT_FOUND);
|
||||
}
|
||||
return this.configRepository.findAllByShopId(shop.id);
|
||||
}
|
||||
|
||||
async findOne(shopId: string, id: string): Promise<Config> {
|
||||
const config = await this.configRepository.findOne({
|
||||
id,
|
||||
shop: { id: shopId },
|
||||
});
|
||||
if (!config) {
|
||||
throw new NotFoundException(`Config with ID ${id} not found`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
async update(shopId: string, id: string, dto: UpdateConfigDto): Promise<Config> {
|
||||
const config = await this.configRepository.findOne({
|
||||
id,
|
||||
shop: { id: shopId },
|
||||
});
|
||||
if (!config) {
|
||||
throw new NotFoundException(`Config with ID ${id} not found`);
|
||||
}
|
||||
|
||||
if (dto.key !== undefined && dto.key !== config.key) {
|
||||
const existing = await this.configRepository.findOne({
|
||||
shop: { id: shopId },
|
||||
key: dto.key,
|
||||
id: { $ne: id },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('این کلید تنظیمات قبلاً برای این فروشگاه ثبت شده است.');
|
||||
}
|
||||
}
|
||||
|
||||
this.em.assign(config, dto);
|
||||
await this.em.persistAndFlush(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
async remove(shopId: string, id: string): Promise<void> {
|
||||
const config = await this.configRepository.findOne({
|
||||
id,
|
||||
shop: { id: shopId },
|
||||
});
|
||||
if (!config) {
|
||||
throw new NotFoundException(`Config with ID ${id} not found`);
|
||||
}
|
||||
await this.em.removeAndFlush(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { Config } from '../entities/config.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigRepository extends EntityRepository<Config> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Config);
|
||||
}
|
||||
|
||||
findAllByShopId(shopId: string): Promise<Config[]> {
|
||||
return this.find({ shop: { id: shopId } }, { orderBy: { key: 'asc' } });
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Collection, Entity, Index, OneToMany, Property } from '@mikro-orm/core'
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { Product } from 'src/modules/products/entities/product.entity';
|
||||
import { Config } from 'src/modules/config/entities/config.entity';
|
||||
|
||||
@Entity({ tableName: 'shops' })
|
||||
@Index({ properties: ['slug'] })
|
||||
@@ -109,4 +110,7 @@ export class Shop extends BaseEntity {
|
||||
@Property({ nullable: true })
|
||||
subscriptionStartDate?: Date;
|
||||
|
||||
@OneToMany(() => Config, config => config.shop)
|
||||
configs = new Collection<Config>(this);
|
||||
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export class ShopService {
|
||||
}
|
||||
|
||||
async findBySlug(slug: string): Promise<Shop> {
|
||||
const shop = await this.em.findOne(Shop, { slug });
|
||||
const shop = await this.em.findOne(Shop, { slug }, { populate: ['configs'] });
|
||||
|
||||
if (!shop) {
|
||||
throw new NotFoundException(ShopMessage.NOT_FOUND);
|
||||
|
||||
Reference in New Issue
Block a user