chore: complete ads service

This commit is contained in:
mahyargdz
2025-02-15 12:08:19 +03:30
parent a13f374031
commit 817d638f3e
16 changed files with 475 additions and 5 deletions
+1
View File
@@ -55,6 +55,7 @@
"cache-manager": "^6.3.2", "cache-manager": "^6.3.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"dayjs": "^1.11.13",
"decimal.js": "^10.5.0", "decimal.js": "^10.5.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"fastify": "^5.2.1", "fastify": "^5.2.1",
+3
View File
@@ -86,6 +86,9 @@ importers:
class-validator: class-validator:
specifier: ^0.14.1 specifier: ^0.14.1
version: 0.14.1 version: 0.14.1
dayjs:
specifier: ^1.11.13
version: 1.11.13
decimal.js: decimal.js:
specifier: ^10.5.0 specifier: ^10.5.0
version: 10.5.0 version: 10.5.0
+2
View File
@@ -14,6 +14,7 @@ import { mailerConfig } from "./configs/mailer.config";
import { rateLimitConfig } from "./configs/rateLimit.config"; import { rateLimitConfig } from "./configs/rateLimit.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 { AdsModule } from "./modules/ads/ads.module";
import { AnnouncementsModule } from "./modules/announcements/announcement.module"; import { AnnouncementsModule } from "./modules/announcements/announcement.module";
import { AuthModule } from "./modules/auth/auth.module"; import { AuthModule } from "./modules/auth/auth.module";
import { ContactUsModule } from "./modules/contact-us/contact-us.module"; import { ContactUsModule } from "./modules/contact-us/contact-us.module";
@@ -57,6 +58,7 @@ import { WalletsModule } from "./modules/wallets/wallets.module";
SubscriptionsModule, SubscriptionsModule,
LearningModule, LearningModule,
DiscountModule, DiscountModule,
AdsModule,
], ],
controllers: [], controllers: [],
providers: [], providers: [],
+31
View File
@@ -126,6 +126,7 @@ export const enum ServiceMessage {
NAME_EXIST = "سرویس با این نام قبلا ثبت شده است", NAME_EXIST = "سرویس با این نام قبلا ثبت شده است",
SERVICE_NOT_FOUND_BY_ID = "سرویسی با این شناسه یافت نشد یا سرویس غیرفعال می‌باشد", SERVICE_NOT_FOUND_BY_ID = "سرویسی با این شناسه یافت نشد یا سرویس غیرفعال می‌باشد",
SERVICE_ID_SHOULD_BE_A_UUID = "شناسه سرویس باید یک UUID باشد", SERVICE_ID_SHOULD_BE_A_UUID = "شناسه سرویس باید یک UUID باشد",
SERVICE_ID_REQUIRED = "شناسه سرویس مورد نیاز است",
} }
export const enum AnnouncementMessage { export const enum AnnouncementMessage {
@@ -371,3 +372,33 @@ export const enum FinancialMessage {
USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID معتبر باشد", USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID معتبر باشد",
FINANCIAL_INFO_ALREADY_EXISTS = "دیتا اطلاعات مالی وجود دارد", FINANCIAL_INFO_ALREADY_EXISTS = "دیتا اطلاعات مالی وجود دارد",
} }
export const enum AdsMessage {
TITLE_REQUIRED = "عنوان الزامی است",
TITLE_STRING = "عنوان باید یک رشته باشد",
IMAGE_SHOULD_BE_URL = "آدرس تصویر باید یک URL معتبر باشد",
LINK_SHOULD_BE_URL = "لینک باید یک URL معتبر باشد",
DISPLAY_LOCATION_REQUIRED = "موقعیت نمایش الزامی است",
TITLE_LENGTH = "طول عنوان باید بین ۳ تا ۵۰ کاراکتر باشد",
INVALID_DISPLAY_LOCATION = "موقعیت نمایش نامعتبر است",
START_DATE_REQUIRED = "تاریخ شروع الزامی است",
INVALID_START_DATE = "تاریخ شروع نامعتبر است",
END_DATE_REQUIRED = "تاریخ پایان الزامی است",
INVALID_END_DATE = "تاریخ پایان نامعتبر است",
IS_ACTIVE_REQUIRED = "وضعیت فعال بودن الزامی است",
IS_ACTIVE_MUST_BE_BOOLEAN = "وضعیت فعال بودن باید true یا false باشد",
IMAGE_URL_REQUIRED = "آدرس تصویر الزامی است",
LINK_REQUIRED = "لینک الزامی است",
ADS_EXIST_WITH_TITLE = "تبلیغی با این عنوان وجود دارد",
START_DATE_MUST_BE_BEFORE_END_DATE = "تاریخ شروع باید قبل از تاریخ پایان باشد",
END_DATE_MUST_BE_AFTER_NOW = "تاریخ پایان باید بعد از تاریخ فعلی باشد",
ADS_CREATED = "تبلیغ با موفقیت ایجاد شد",
ADS_NOT_FOUND_BY_ID = "تبلیغی با این شناسه یافت نشد",
ADS_UPDATED = "تبلیغ با موفقیت به روز رسانی شد",
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
START_DATE_MUST_BE_AFTER_NOW = "تاریخ شروع باید بعد از تاریخ فعلی باشد",
INVALID_DATE = "تاریخ نامعتبر است",
SERVICE_ADS_MUST_BE_DISPLAYED_ON_SERVICE_PAGE = "تبلیغات سرویس باید در خود سرویس نمایش داده شود",
ADS_DELETED = "تبلیغ با موفقیت حذف شد",
ADS_ACTIVATED = "تبلیغ با موفقیت فعال شد",
ADS_DEACTIVATED = "تبلیغ با موفقیت غیرفعال شد",
}
@@ -0,0 +1,32 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsDateString, IsEnum, IsOptional, IsString } from "class-validator";
import { PaginationDto } from "../../../common/DTO/pagination.dto";
import { AdsMessage } from "../../../common/enums/message.enum";
import { AdsDisplayLocation } from "../enums/ads-location.enum";
export class AdsSearchQueryDto extends PaginationDto {
@IsOptional()
@Type(() => Number)
// @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN })
@ApiPropertyOptional({ description: "ads status", example: 1 })
isActive?: number;
@IsOptional()
@IsString({ message: AdsMessage.SEARCH_QUERY_STRING })
@ApiPropertyOptional({ description: "Search query", example: "search query" })
q?: string;
@IsOptional()
@IsDateString({}, { message: AdsMessage.INVALID_DATE })
@ApiPropertyOptional({ description: "since query", example: "2025-01-27" })
since?: string;
}
export class GetAdsByLocationQueryDto {
@IsOptional()
@IsEnum(AdsDisplayLocation)
@ApiPropertyOptional({ description: "Ads display location", example: AdsDisplayLocation.OTHER_SERVICES_LEFT })
location?: AdsDisplayLocation;
}
+53
View File
@@ -0,0 +1,53 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator";
import { AdsMessage, ServiceMessage } from "../../../common/enums/message.enum";
import { AdsDisplayLocation } from "../enums/ads-location.enum";
export class CreateAdsDto {
@IsNotEmpty({ message: AdsMessage.TITLE_REQUIRED })
@IsString({ message: AdsMessage.TITLE_STRING })
@Length(3, 150, { message: AdsMessage.TITLE_LENGTH })
@ApiProperty({ description: "The title of the ad", example: "New product ads" })
title: string;
@IsNotEmpty({ message: AdsMessage.DISPLAY_LOCATION_REQUIRED })
@IsEnum(AdsDisplayLocation)
@ApiProperty({
description: "The location where the ad will be displayed",
example: AdsDisplayLocation.HOMEPAGE_TOP,
enum: AdsDisplayLocation,
})
displayLocation: AdsDisplayLocation;
@IsNotEmpty({ message: AdsMessage.START_DATE_REQUIRED })
@IsDateString({}, { message: AdsMessage.INVALID_START_DATE })
@ApiProperty({ description: "The date when the ad will start displaying", example: "2022-01-01T00:00:00.000Z" })
startDate: Date;
@IsNotEmpty({ message: AdsMessage.END_DATE_REQUIRED })
@IsDateString({}, { message: AdsMessage.INVALID_END_DATE })
@ApiProperty({ description: "The date when the ad will stop displaying", example: "2022-01-31T23:59:59.999Z" })
endDate: Date;
@IsNotEmpty({ message: AdsMessage.IS_ACTIVE_REQUIRED })
@IsBoolean({ message: AdsMessage.IS_ACTIVE_MUST_BE_BOOLEAN })
@ApiProperty({ description: "The status of the ad", example: true })
isActive: boolean;
@IsNotEmpty({ message: AdsMessage.IMAGE_URL_REQUIRED })
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: AdsMessage.IMAGE_SHOULD_BE_URL })
@ApiProperty({ description: "The URL of the image to be displayed", example: "https://test.com/pic.png" })
imageUrl: string;
@IsNotEmpty({ message: AdsMessage.LINK_REQUIRED })
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: AdsMessage.LINK_SHOULD_BE_URL })
@ApiProperty({ description: "The URL to redirect to when the ad is clicked", example: "https://test.com" })
link: string;
@IsOptional()
@IsNotEmpty({ message: ServiceMessage.SERVICE_ID_REQUIRED })
@IsUUID("4", { message: ServiceMessage.SERVICE_ID_SHOULD_BE_A_UUID })
@ApiPropertyOptional({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
serviceId: string;
}
+5
View File
@@ -0,0 +1,5 @@
import { PartialType } from "@nestjs/swagger";
import { CreateAdsDto } from "./create-ads.dto";
export class UpdateAdsDto extends PartialType(CreateAdsDto) {}
+74
View File
@@ -0,0 +1,74 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { AdsSearchQueryDto, GetAdsByLocationQueryDto } from "./DTO/ads-search-query.dto";
import { CreateAdsDto } from "./DTO/create-ads.dto";
import { UpdateAdsDto } from "./DTO/update-ads.dto";
import { AdsService } from "./providers/ads.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { Roles } from "../../common/decorators/roles.decorator";
import { ParamDto } from "../../common/DTO/param.dto";
import { RoleEnum } from "../users/enums/role.enum";
@Controller("advertise")
@ApiTags("Advertisements")
@AuthGuards()
export class AdsController {
constructor(private readonly adsService: AdsService) {}
@ApiOperation({ summary: "Create new ads" })
@Roles(RoleEnum.ADMIN)
@Post()
createAds(@Body() createDto: CreateAdsDto) {
return this.adsService.createAds(createDto);
}
@ApiOperation({ summary: "Get all ads" })
@Roles(RoleEnum.ADMIN)
@Get("list")
getAdsList(@Query() queryDto: AdsSearchQueryDto) {
return this.adsService.getAdsList(queryDto);
}
@ApiOperation({ summary: "Get ads by id" })
@Roles(RoleEnum.ADMIN)
@Get(":id")
getAdsById(@Param() paramDto: ParamDto) {
return this.adsService.getAdsById(paramDto.id);
}
@ApiOperation({ summary: "Update ads by id" })
@Roles(RoleEnum.ADMIN)
@Patch(":id")
updateAdsById(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdsDto) {
return this.adsService.updateAdsById(paramDto.id, updateDto);
}
@ApiOperation({ summary: "Delete ads by id" })
@Roles(RoleEnum.ADMIN)
@Delete(":id")
deleteAdsById(@Param() paramDto: ParamDto) {
return this.adsService.deleteAdsById(paramDto.id);
}
@ApiOperation({ summary: "Toggle ads status" })
@Roles(RoleEnum.ADMIN)
@Patch("toggle-status/:id")
toggleAdsStatus(@Param() paramDto: ParamDto) {
return this.adsService.toggleAdsStatus(paramDto.id);
}
@ApiOperation({ summary: "Get ads by location ==> both user and admin" })
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
@Get("by-location")
getAdsByLocation(@Query() queryDto: GetAdsByLocationQueryDto) {
return this.adsService.getAdsByLocation(queryDto);
}
@ApiOperation({ summary: "Get all ads grouped by location ==> both user and admin" })
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
@Get("grouped-by-location")
getAdsGroupedByLocation() {
return this.adsService.getAdsGroupedByLocation();
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AdsController } from "./ads.controller";
import { Ads } from "./entities/ads.entity";
import { AdsService } from "./providers/ads.service";
import { AdsRepository } from "./repositories/ads.repository";
import { DanakServicesModule } from "../danak-services/danak-services.module";
@Module({
imports: [TypeOrmModule.forFeature([Ads]), DanakServicesModule],
controllers: [AdsController],
providers: [AdsService, AdsRepository],
exports: [AdsService],
})
export class AdsModule {}
+32
View File
@@ -0,0 +1,32 @@
import { Column, Entity, ManyToOne } from "typeorm";
import { BaseEntity } from "../../../common/entities/base.entity";
import { DanakService } from "../../danak-services/entities/danak-service.entity";
import { AdsDisplayLocation } from "../enums/ads-location.enum";
@Entity()
export class Ads extends BaseEntity {
@Column({ type: "varchar", length: 150, unique: true })
title: string;
@Column({ type: "enum", enum: AdsDisplayLocation, nullable: false })
displayLocation: AdsDisplayLocation;
@Column({ type: "timestamptz", nullable: false })
startDate: Date;
@Column({ type: "timestamptz", nullable: false })
endDate: Date;
@Column({ type: "boolean", default: true })
isActive: boolean;
@Column({ type: "varchar", length: 250, nullable: false })
imageUrl: string;
@Column({ type: "varchar", length: 250, nullable: false })
link: string;
@ManyToOne(() => DanakService, (danakService) => danakService.ads, { nullable: true, onDelete: "SET NULL" })
service: DanakService | null;
}
@@ -0,0 +1,8 @@
export enum AdsDisplayLocation {
HOMEPAGE_TOP = "HOMEPAGE_TOP",
SINGLE_SERVICE_PAGE = "SINGLE_SERVICE_PAGE",
MY_SERVICES_PAGE = "MY_SERVICES_PAGE",
OTHER_SERVICES_TOP = "OTHER_SERVICES_TOP",
OTHER_SERVICES_LEFT = "OTHER_SERVICES_LEFT",
SERVICE = "SERVICE",
}
+196
View File
@@ -0,0 +1,196 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs";
import { FindOptionsWhere, LessThanOrEqual, MoreThan, Not } from "typeorm";
import { AdsMessage, ServiceMessage } from "../../../common/enums/message.enum";
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { AdsSearchQueryDto, GetAdsByLocationQueryDto } from "../DTO/ads-search-query.dto";
import { CreateAdsDto } from "../DTO/create-ads.dto";
import { UpdateAdsDto } from "../DTO/update-ads.dto";
import { Ads } from "../entities/ads.entity";
import { AdsDisplayLocation } from "../enums/ads-location.enum";
import { AdsRepository } from "../repositories/ads.repository";
@Injectable()
export class AdsService {
constructor(
private readonly adsRepository: AdsRepository,
private readonly danakServicesService: DanakServicesService,
) {}
//
async createAds(createDto: CreateAdsDto) {
const existAds = await this.adsRepository.findOneBy({ title: createDto.title });
if (existAds) throw new BadRequestException(AdsMessage.ADS_EXIST_WITH_TITLE);
let danakService = null;
if (createDto.serviceId) {
danakService = await this.danakServicesService.findServiceById(createDto.serviceId);
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
if (createDto.displayLocation !== AdsDisplayLocation.SERVICE)
throw new BadRequestException(AdsMessage.SERVICE_ADS_MUST_BE_DISPLAYED_ON_SERVICE_PAGE);
}
this.validateDates(createDto.startDate, createDto.endDate);
const ads = this.adsRepository.create({ ...createDto, service: danakService });
await this.adsRepository.save(ads);
//
return {
message: AdsMessage.ADS_CREATED,
ads,
};
}
//***************************************** */
async getAdsById(adsId: string) {
const ads = await this.findAdsById(adsId);
return {
ads,
};
}
//***************************************** */
async getAdsGroupedByLocation() {
const now = new Date();
const adsGroups = await this.adsRepository
.createQueryBuilder("ads")
.select(["ads.displayLocation", "jsonb_agg(ads.*) AS ads"])
.where("ads.isActive = :isActive", { isActive: true })
.andWhere("ads.startDate <= :now", { now })
.andWhere("ads.endDate > :now", { now })
.groupBy("ads.displayLocation")
.getRawMany();
return { adsGroups };
}
//***************************************** */
async updateAdsById(adsId: string, updateDto: UpdateAdsDto) {
const ads = await this.findAdsById(adsId);
if (updateDto.title) {
const existAds = await this.adsRepository.findOneBy({ title: updateDto.title, id: Not(adsId) });
if (existAds) throw new BadRequestException(AdsMessage.ADS_EXIST_WITH_TITLE);
}
if (updateDto.startDate && updateDto.endDate) {
this.validateDates(updateDto.startDate, updateDto.endDate);
}
let danakService = null;
if (updateDto.serviceId) {
danakService = await this.danakServicesService.findServiceById(updateDto.serviceId);
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
}
await this.adsRepository.save({ ...ads, ...updateDto, service: danakService });
return {
message: AdsMessage.ADS_UPDATED,
};
}
//****************************************** */
async getAdsByLocation(queryDto: GetAdsByLocationQueryDto) {
const whereClause: FindOptionsWhere<Ads> = {
isActive: true,
startDate: LessThanOrEqual(new Date()),
endDate: MoreThan(new Date()),
};
if (queryDto.location) {
whereClause.displayLocation = queryDto.location;
}
const ads = await this.adsRepository.find({
where: whereClause,
});
return { ads };
}
//***************************************** */
async deleteAdsById(adsId: string) {
const ads = await this.findAdsById(adsId);
await this.adsRepository.remove(ads);
return {
message: AdsMessage.ADS_DELETED,
};
}
//***************************************** */
async toggleAdsStatus(adsId: string) {
const ads = await this.findAdsById(adsId);
ads.isActive = !ads.isActive;
//
await this.adsRepository.save(ads);
return {
message: ads.isActive ? AdsMessage.ADS_ACTIVATED : AdsMessage.ADS_DEACTIVATED,
isActive: ads.isActive,
};
}
//***************************************** */
async getAdsList(queryDto: AdsSearchQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.adsRepository.createQueryBuilder("ads");
queryBuilder.leftJoinAndSelect("ads.service", "service", "service.id IS NOT NULL");
if (queryDto.isActive !== undefined) {
queryBuilder.andWhere("ads.isActive = :isActive", { isActive: queryDto.isActive === 1 });
}
if (queryDto.q) {
queryBuilder.andWhere("ads.title ILIKE :q", { q: `%${queryDto.q}%` });
}
if (queryDto.since) {
queryBuilder.andWhere("(ads.createdAt >= :since OR ads.startDate >= :since OR ads.endDate >= :since)", {
since: new Date(queryDto.since),
});
}
queryBuilder.orderBy("ads.createdAt", "DESC").skip(skip).take(limit);
const [ads, count] = await queryBuilder.getManyAndCount();
return {
ads,
count,
paginate: true,
};
}
//***************************************** */
async findAdsById(adsId: string) {
const ads = await this.adsRepository.findOne({ where: { id: adsId }, relations: { service: true } });
if (!ads) throw new BadRequestException(AdsMessage.ADS_NOT_FOUND_BY_ID);
return ads;
}
//***************************************** */
private validateDates(startDate: Date, endDate: Date) {
if (dayjs(startDate).isAfter(dayjs(endDate))) {
throw new BadRequestException(AdsMessage.START_DATE_MUST_BE_BEFORE_END_DATE);
}
if (dayjs(startDate).isBefore(dayjs())) {
throw new BadRequestException(AdsMessage.START_DATE_MUST_BE_AFTER_NOW);
}
if (dayjs(startDate).isSame(dayjs(endDate))) {
throw new BadRequestException(AdsMessage.START_DATE_MUST_BE_BEFORE_END_DATE);
}
if (dayjs(endDate).isBefore(dayjs())) {
throw new BadRequestException(AdsMessage.END_DATE_MUST_BE_AFTER_NOW);
}
}
//***************************************** */
}
@@ -0,0 +1,12 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { Ads } from "../entities/ads.entity";
@Injectable()
export class AdsRepository extends Repository<Ads> {
constructor(@InjectRepository(Ads) adsRepository: Repository<Ads>) {
super(adsRepository.target, adsRepository.manager, adsRepository.queryRunner);
}
}
@@ -46,10 +46,7 @@ export class CreateAnnouncementDto {
@IsOptional() @IsOptional()
@IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID }) @IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID })
@IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID }) @IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID })
@ApiProperty({ @ApiProperty({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
description: "Service ID",
example: "d290f1ee-6c54-4b01-90e6-d701748f0851",
})
serviceId: string; serviceId: string;
@IsOptional() @IsOptional()
@@ -3,6 +3,7 @@ import { Column, Entity, 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";
import { BaseEntity } from "../../../common/entities/base.entity"; import { BaseEntity } from "../../../common/entities/base.entity";
import { Ads } from "../../ads/entities/ads.entity";
import { Announcement } from "../../announcements/entities/announcement.entity"; import { Announcement } from "../../announcements/entities/announcement.entity";
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
import { Ticket } from "../../tickets/entities/ticket.entity"; import { Ticket } from "../../tickets/entities/ticket.entity";
@@ -49,6 +50,7 @@ export class DanakService extends BaseEntity {
@Column({ type: "varchar", length: 255, nullable: false }) @Column({ type: "varchar", length: 255, nullable: false })
icon: string; icon: string;
//-------------------------------------
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, { @ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, {
nullable: false, nullable: false,
onDelete: "RESTRICT", onDelete: "RESTRICT",
@@ -66,6 +68,12 @@ export class DanakService extends BaseEntity {
@OneToMany(() => SubscriptionPlan, (plan) => plan.service, { cascade: true }) @OneToMany(() => SubscriptionPlan, (plan) => plan.service, { cascade: true })
subscriptionPlans: SubscriptionPlan[]; subscriptionPlans: SubscriptionPlan[];
@OneToMany(() => Ads, (ads) => ads.service)
ads: Ads[];
// @ManyToMany(() => Discount, (discount) => discount.services, { nullable: true })
// discounts?: Discount[];
} }
// @ManyToMany(() => User, (user) => user.danakServices) // @ManyToMany(() => User, (user) => user.danakServices)
@@ -66,7 +66,7 @@ export class DanakServicesService {
} }
if (queryDto.q) { if (queryDto.q) {
queryBuilder.andWhere("category.title LIKE :q", { q: `%${queryDto.q}%` }); queryBuilder.andWhere("category.title ILIKE :q", { q: `%${queryDto.q}%` });
} }
queryBuilder queryBuilder