|
|
@@ -8,8 +8,17 @@ import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
|
|
import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
|
|
|
import { CacheSyncService } from '../../../cache-sync/cache-sync.service';
|
|
|
import { ImageUploadService } from '../image-upload/image-upload.service';
|
|
|
-import { CreateAdsDto, ListAdsDto, UpdateAdsDto } from './ads.dto';
|
|
|
+import type { MultipartFile } from '@fastify/multipart';
|
|
|
+import * as fs from 'fs/promises';
|
|
|
+import * as path from 'path';
|
|
|
+import {
|
|
|
+ CreateAdsDto,
|
|
|
+ ListAdsDto,
|
|
|
+ UpdateAdsDto,
|
|
|
+ AdsInterfaceDto,
|
|
|
+} from './ads.dto';
|
|
|
import { CommonStatus } from '../common/status.enum';
|
|
|
+import { ImageUrlBuilderService } from './image/image-url-builder.service';
|
|
|
|
|
|
@Injectable()
|
|
|
export class AdsService {
|
|
|
@@ -17,6 +26,7 @@ export class AdsService {
|
|
|
private readonly mongoPrismaService: MongoPrismaService,
|
|
|
private readonly cacheSyncService: CacheSyncService,
|
|
|
private readonly imageUploadService: ImageUploadService,
|
|
|
+ private readonly imageUrlBuilderService: ImageUrlBuilderService,
|
|
|
) {}
|
|
|
|
|
|
/**
|
|
|
@@ -62,6 +72,27 @@ export class AdsService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private mapToDto(ad: any): AdsInterfaceDto {
|
|
|
+ return {
|
|
|
+ id: ad.id,
|
|
|
+ adsModuleId: ad.adsModuleId,
|
|
|
+ advertiser: ad.advertiser,
|
|
|
+ title: ad.title,
|
|
|
+ adsContent: ad.adsContent,
|
|
|
+ adsUrl: ad.adsUrl,
|
|
|
+ imgSource: ad.imgSource,
|
|
|
+ adsCoverImg: ad.adsCoverImg,
|
|
|
+ adsCoverImgUrl: this.imageUrlBuilderService.buildAdsCoverUrl(ad),
|
|
|
+
|
|
|
+ startDt: ad.startDt,
|
|
|
+ expiryDt: ad.expiryDt,
|
|
|
+ seq: ad.seq,
|
|
|
+ status: ad.status,
|
|
|
+ createAt: ad.createAt,
|
|
|
+ updateAt: ad.updateAt,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
async create(dto: CreateAdsDto) {
|
|
|
this.ensureTimeRange(dto.startDt, dto.expiryDt);
|
|
|
|
|
|
@@ -88,7 +119,8 @@ export class AdsService {
|
|
|
// Auto-schedule cache refresh (per-ad + pool)
|
|
|
await this.cacheSyncService.scheduleAdRefresh(ad.id, ad.adsModule.adType);
|
|
|
|
|
|
- return ad;
|
|
|
+ // Return created ad mapped to AdsInterfaceDto
|
|
|
+ return this.mapToDto(ad);
|
|
|
}
|
|
|
|
|
|
async update(dto: UpdateAdsDto) {
|
|
|
@@ -126,7 +158,7 @@ export class AdsService {
|
|
|
// Auto-schedule cache refresh (per-ad + pool)
|
|
|
await this.cacheSyncService.scheduleAdRefresh(ad.id, ad.adsModule.adType);
|
|
|
|
|
|
- return ad;
|
|
|
+ return this.mapToDto(ad);
|
|
|
} catch (e) {
|
|
|
if (e instanceof PrismaClientKnownRequestError && e.code === 'P2025') {
|
|
|
throw new NotFoundException('Ads not found');
|
|
|
@@ -145,7 +177,7 @@ export class AdsService {
|
|
|
throw new NotFoundException('Ads not found');
|
|
|
}
|
|
|
|
|
|
- return row;
|
|
|
+ return this.mapToDto(row);
|
|
|
}
|
|
|
|
|
|
async list(dto: ListAdsDto) {
|
|
|
@@ -181,9 +213,11 @@ export class AdsService {
|
|
|
}),
|
|
|
]);
|
|
|
|
|
|
+ const mappedData = data.map((ad) => this.mapToDto(ad));
|
|
|
+
|
|
|
return {
|
|
|
total,
|
|
|
- data,
|
|
|
+ data: mappedData,
|
|
|
totalPages: Math.ceil(total / size),
|
|
|
page,
|
|
|
size,
|
|
|
@@ -224,8 +258,9 @@ export class AdsService {
|
|
|
|
|
|
/**
|
|
|
* Upload and update Ads cover image.
|
|
|
+ * Deletes old image file before uploading new one.
|
|
|
*/
|
|
|
- async updateAdsCover(id: string, file: Express.Multer.File) {
|
|
|
+ async updateAdsCover(id: string, file: MultipartFile) {
|
|
|
// Ensure ad exists
|
|
|
const ad = await this.mongoPrismaService.ads.findUnique({
|
|
|
where: { id },
|
|
|
@@ -235,7 +270,24 @@ export class AdsService {
|
|
|
throw new NotFoundException('Ads not found');
|
|
|
}
|
|
|
|
|
|
- // Upload image
|
|
|
+ // Delete old image file if exists
|
|
|
+ if (ad.adsCoverImg && ad.imgSource === 'LOCAL_ONLY') {
|
|
|
+ const localRoot = process.env.BOX_IMAGE_LOCAL_ROOT || '/tmp/box-images';
|
|
|
+ const oldFilePath = path.join(localRoot, ad.adsCoverImg);
|
|
|
+
|
|
|
+ try {
|
|
|
+ await fs.unlink(oldFilePath);
|
|
|
+ console.log(`[AdsService] Deleted old cover image: ${oldFilePath}`);
|
|
|
+ } catch (err) {
|
|
|
+ // Log error but don't fail the upload
|
|
|
+ console.warn(
|
|
|
+ `[AdsService] Failed to delete old image: ${oldFilePath}`,
|
|
|
+ err,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Upload new image
|
|
|
const { key, imgSource } = await this.imageUploadService.uploadCoverImage(
|
|
|
'ads-cover',
|
|
|
file,
|