update: add slug field to the danak service entity

This commit is contained in:
mahyargdz
2025-05-04 11:31:53 +03:30
parent 721968ed2e
commit 8fe1d398a6
9 changed files with 55 additions and 24 deletions
-14
View File
@@ -1,14 +0,0 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "nestjs"
static_configs:
- targets: ["localhost:3000"]
metrics_path: "/metrics"
scrape_interval: 5s
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
+3 -3
View File
@@ -7,13 +7,13 @@ import { ConfigModule } from "@nestjs/config";
import { ThrottlerModule } from "@nestjs/throttler"; import { ThrottlerModule } from "@nestjs/throttler";
import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmModule } from "@nestjs/typeorm";
import { MailerModule } from "@nestjs-modules/mailer"; import { MailerModule } from "@nestjs-modules/mailer";
// import { TelegrafModule } from "nestjs-telegraf"; import { TelegrafModule } from "nestjs-telegraf";
import { bullMqConfig } from "./configs/bullmq.config"; import { bullMqConfig } from "./configs/bullmq.config";
import { cacheConfig } from "./configs/cache.config"; import { cacheConfig } from "./configs/cache.config";
import { mailerConfig } from "./configs/mailer.config"; import { mailerConfig } from "./configs/mailer.config";
import { rateLimitConfig } from "./configs/rateLimit.config"; import { rateLimitConfig } from "./configs/rateLimit.config";
// import { telegrafConfig } from "./configs/telegraf.config"; import { telegrafConfig } from "./configs/telegraf.config";
import { databaseConfigs } from "./configs/typeorm.config"; import { databaseConfigs } from "./configs/typeorm.config";
import { HTTPLogger } from "./core/middlewares/logger.middleware"; import { HTTPLogger } from "./core/middlewares/logger.middleware";
import { AddressModule } from "./modules/address/address.module"; import { AddressModule } from "./modules/address/address.module";
@@ -43,7 +43,7 @@ import { MonitoringModule } from "./monitoring/monitoring.module";
@Module({ @Module({
imports: [ imports: [
// TelegrafModule.forRootAsync(telegrafConfig()), TelegrafModule.forRootAsync(telegrafConfig()),
MailerModule.forRootAsync(mailerConfig()), MailerModule.forRootAsync(mailerConfig()),
BullModule.forRootAsync(bullMqConfig()), BullModule.forRootAsync(bullMqConfig()),
ThrottlerModule.forRootAsync(rateLimitConfig()), ThrottlerModule.forRootAsync(rateLimitConfig()),
+5
View File
@@ -198,6 +198,10 @@ export const enum ServiceMessage {
SHOW_IN_SLIDER_BOOLEAN = "وضعیت نمایش در اسلایدر سرویس باید یک بولین باشد", SHOW_IN_SLIDER_BOOLEAN = "وضعیت نمایش در اسلایدر سرویس باید یک بولین باشد",
COVER_URL_REQUIRED = "آدرس عکس کاور سرویس مورد نیاز است", COVER_URL_REQUIRED = "آدرس عکس کاور سرویس مورد نیاز است",
COVER_URL_SHOULD_BE_URL = "آدرس عکس کاور سرویس باید یک آدرس یو آر ال باشد", COVER_URL_SHOULD_BE_URL = "آدرس عکس کاور سرویس باید یک آدرس یو آر ال باشد",
SLUG_REQUIRED = "شناسه منحصر به فرد سرویس مورد نیاز است",
SLUG_STRING = "شناسه منحصر به فرد سرویس باید یک رشته باشد",
SLUG_LENGTH = "شناسه منحصر به فرد سرویس باید بین ۳ تا ۱۵۰ کاراکتر باشد",
SERVICE_ALREADY_EXISTS_WITH_THIS_SLUG = "سرویسی با این اسلاگ قبلا ثبت شده است",
} }
export const enum AnnouncementMessage { export const enum AnnouncementMessage {
@@ -731,6 +735,7 @@ export const enum BlogMessage {
SLUG_REQUIRED = "اسلاگ بلاگ الزامی است", SLUG_REQUIRED = "اسلاگ بلاگ الزامی است",
SLUG_MAX_LENGTH = "حداکثر طول اسلاگ بلاگ باید ۲۵۵ کاراکتر باشد", SLUG_MAX_LENGTH = "حداکثر طول اسلاگ بلاگ باید ۲۵۵ کاراکتر باشد",
CATEGORY_HAS_BLOGS = "دسته بندی مورد نظر دارای بلاگ می‌باشد", CATEGORY_HAS_BLOGS = "دسته بندی مورد نظر دارای بلاگ می‌باشد",
BLOG_ALREADY_EXISTS_WITH_THIS_SLUG = "بلاگی با این اسلاگ قبلا ثبت شده است",
} }
export const enum SliderMessage { export const enum SliderMessage {
+1 -1
View File
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
username: configService.getOrThrow<string>("DB_USER"), username: configService.getOrThrow<string>("DB_USER"),
password: configService.getOrThrow<string>("DB_PASS"), password: configService.getOrThrow<string>("DB_PASS"),
autoLoadEntities: true, autoLoadEntities: true,
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : false, synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true, logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
migrationsTableName: "typeorm_migrations", migrationsTableName: "typeorm_migrations",
migrationsRun: false, migrationsRun: false,
+4 -1
View File
@@ -111,6 +111,9 @@ export class BlogsService {
const category = await this.findCategoryById(createBlogDto.categoryId); const category = await this.findCategoryById(createBlogDto.categoryId);
const existSlug = await this.blogsRepository.findOne({ where: { slug: createBlogDto.slug } });
if (existSlug) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS_WITH_THIS_SLUG);
const slug = this.generateSlug(createBlogDto.slug); const slug = this.generateSlug(createBlogDto.slug);
const blog = this.blogsRepository.create({ const blog = this.blogsRepository.create({
@@ -145,7 +148,7 @@ export class BlogsService {
if (updateBlogDto.slug) { if (updateBlogDto.slug) {
const existBlog = await this.blogsRepository.findOne({ where: { slug: updateBlogDto.slug, id: Not(id) } }); const existBlog = await this.blogsRepository.findOne({ where: { slug: updateBlogDto.slug, id: Not(id) } });
if (existBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS); if (existBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS_WITH_THIS_SLUG);
} }
await this.blogsRepository.save({ await this.blogsRepository.save({
@@ -103,4 +103,11 @@ export class CreateServiceDto {
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: ServiceMessage.COVER_URL_SHOULD_BE_URL }) @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: ServiceMessage.COVER_URL_SHOULD_BE_URL })
@ApiProperty({ description: "The cover url of the service", example: "https://example.com/cover.png" }) @ApiProperty({ description: "The cover url of the service", example: "https://example.com/cover.png" })
coverUrl?: string; coverUrl?: string;
@IsOptional()
@IsNotEmpty({ message: ServiceMessage.SLUG_REQUIRED })
@IsString({ message: ServiceMessage.SLUG_STRING })
@Length(3, 150, { message: ServiceMessage.SLUG_LENGTH })
@ApiProperty({ description: "The slug of the service", example: "service-slug" })
slug?: string;
} }
@@ -1,4 +1,4 @@
import { Column, DeleteDateColumn, Entity, ManyToOne, OneToMany } from "typeorm"; import { Column, DeleteDateColumn, Entity, Index, ManyToOne, OneToMany } from "typeorm";
import { DanakServiceCategory } from "./danak-service-category.entity"; import { DanakServiceCategory } from "./danak-service-category.entity";
import { DanakServiceImage } from "./danak-service-image.entity"; import { DanakServiceImage } from "./danak-service-image.entity";
@@ -13,10 +13,15 @@ import { ServicesLanguage } from "../enums/services-language.enum";
@Entity() @Entity()
export class DanakService extends BaseEntity { export class DanakService extends BaseEntity {
@Column({ type: "varchar", length: 150, nullable: false, unique: true }) @Column({ type: "varchar", length: 150, nullable: false, unique: true })
@Index()
name: string; name: string;
@Column({ type: "varchar", length: 150, nullable: true }) @Column({ type: "varchar", length: 150, nullable: true })
title: string; title: string | null;
@Column({ type: "varchar", length: 150, nullable: true, unique: true })
@Index()
slug: string | null;
@Column({ type: "boolean", nullable: false, default: false }) @Column({ type: "boolean", nullable: false, default: false })
isDanakSuggest: boolean; isDanakSuggest: boolean;
@@ -55,7 +60,7 @@ export class DanakService extends BaseEntity {
icon: string; icon: string;
@Column({ type: "varchar", length: 255, nullable: true }) @Column({ type: "varchar", length: 255, nullable: true })
coverUrl: string; coverUrl: string | null;
//------------------------------------- //-------------------------------------
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, { @ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, {
nullable: false, nullable: false,
@@ -271,11 +271,13 @@ export class DanakServicesService {
const existService = await this.danakServicesRepository.findOneByName(createDto.name); const existService = await this.danakServicesRepository.findOneByName(createDto.name);
if (existService) throw new BadRequestException(ServiceMessage.NAME_EXIST); if (existService) throw new BadRequestException(ServiceMessage.NAME_EXIST);
const slug = this.generateSlug(createDto.slug ?? createDto.title);
const images = createDto.images.map((image) => { const images = createDto.images.map((image) => {
return { imageUrl: image }; return { imageUrl: image };
}); });
const service = this.danakServicesRepository.create({ ...createDto, category, images }); const service = this.danakServicesRepository.create({ ...createDto, category, images, slug });
await this.danakServicesRepository.save(service); await this.danakServicesRepository.save(service);
return { return {
message: CommonMessage.CREATED, message: CommonMessage.CREATED,
@@ -314,7 +316,17 @@ export class DanakServicesService {
images = newImages; images = newImages;
} }
await this.danakServicesRepository.save({ ...service, ...updateDto, images: images.length ? images : service.images }); if (updateDto.slug) {
const existSlug = await this.danakServicesRepository.findOne({ where: { slug: updateDto.slug, id: Not(serviceId) } });
if (existSlug) throw new BadRequestException(ServiceMessage.SERVICE_ALREADY_EXISTS_WITH_THIS_SLUG);
}
await this.danakServicesRepository.save({
...service,
...updateDto,
slug: this.generateSlug(updateDto.slug ?? service.slug ?? service.name),
images: images.length ? images : service.images,
});
return { return {
message: CommonMessage.UPDATE_SUCCESS, message: CommonMessage.UPDATE_SUCCESS,
service, service,
@@ -555,6 +567,18 @@ export class DanakServicesService {
} }
/******************************************** */ /******************************************** */
private generateSlug(slug: string): string {
if (!slug) return "";
// Replace spaces and special characters with hyphens, preserve Farsi characters
return slug
.trim()
.replace(/[&\\#,+()$~%.'":*?<>{}]/g, "") // Remove special characters
.replace(/\s+/g, "-") // Replace spaces with hyphens
.replace(/-+/g, "-") // Replace multiple hyphens with single hyphen
.trim();
}
private async checkUserPurchasedService(userId: string, serviceId: string) { private async checkUserPurchasedService(userId: string, serviceId: string) {
const subscription = await this.danakServicesRepository const subscription = await this.danakServicesRepository
.createQueryBuilder() .createQueryBuilder()
@@ -175,6 +175,7 @@ export class DiscountsService {
count, count,
}; };
} }
/** /**
* Soft delete a discount * Soft delete a discount
*/ */