chore: complete ads service
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
Generated
+3
@@ -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
|
||||||
|
|||||||
@@ -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: [],
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PartialType } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateAdsDto } from "./create-ads.dto";
|
||||||
|
|
||||||
|
export class UpdateAdsDto extends PartialType(CreateAdsDto) {}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user