| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import {
- Injectable,
- BadRequestException,
- NotFoundException,
- } from '@nestjs/common';
- import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
- import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
- import { CreateAdsDto, ListAdsDto, UpdateAdsDto } from './ads.dto';
- import { CommonStatus } from '../common/status.enum';
- @Injectable()
- export class AdsService {
- constructor(private readonly mongoPrismaService: MongoPrismaService) {}
- /**
- * Current epoch time in milliseconds.
- *
- * NOTE:
- * - Kept as `number` for backward compatibility.
- * - If you migrate to BigInt timestamps, update this and the Prisma schema.
- */
- private now(): number {
- return Date.now();
- }
- private trimOptional(value?: string | null) {
- if (value === undefined) return undefined;
- if (value === null) return null;
- return typeof value === 'string' ? value.trim() : value;
- }
- /**
- * Validate that expiryDt is not earlier than startDt.
- * Throws 400 if invalid.
- */
- private ensureTimeRange(startDt: number, expiryDt: number): void {
- if (expiryDt < startDt) {
- throw new BadRequestException(
- 'expiryDt must be greater than or equal to startDt',
- );
- }
- }
- /**
- * Ensure the channel exists.
- */
- private async assertChannelExists(channelId: string): Promise<void> {
- const exists = await this.mongoPrismaService.channel.findUnique({
- where: { id: channelId },
- select: { id: true },
- });
- if (!exists) {
- throw new NotFoundException('Channel not found');
- }
- }
- async create(dto: CreateAdsDto) {
- await this.assertChannelExists(dto.channelId);
- this.ensureTimeRange(dto.startDt, dto.expiryDt);
- const now = this.now();
- return this.mongoPrismaService.ads.create({
- data: {
- channelId: dto.channelId,
- adsModuleId: dto.adsModuleId,
- advertiser: dto.advertiser,
- title: dto.title,
- adsContent: this.trimOptional(dto.adsContent) ?? null,
- adsCoverImg: this.trimOptional(dto.adsCoverImg) ?? null,
- adsUrl: this.trimOptional(dto.adsUrl) ?? null,
- startDt: dto.startDt,
- expiryDt: dto.expiryDt,
- seq: dto.seq ?? 0,
- status: dto.status ?? CommonStatus.enabled,
- createAt: now,
- updateAt: now,
- },
- include: { channel: true, adsModule: true },
- });
- }
- async update(dto: UpdateAdsDto) {
- await this.assertChannelExists(dto.channelId);
- this.ensureTimeRange(dto.startDt, dto.expiryDt);
- const now = this.now();
- // Build data object carefully to avoid unintended field changes
- const data: any = {
- channelId: dto.channelId,
- adsModuleId: dto.adsModuleId,
- advertiser: dto.advertiser,
- title: dto.title,
- adsContent: this.trimOptional(dto.adsContent) ?? null,
- adsCoverImg: this.trimOptional(dto.adsCoverImg) ?? null,
- adsUrl: this.trimOptional(dto.adsUrl) ?? null,
- startDt: dto.startDt,
- expiryDt: dto.expiryDt,
- seq: dto.seq ?? 0,
- updateAt: now,
- };
- // Only update status if explicitly provided,
- // to avoid silently re-enabling disabled ads.
- if (dto.status !== undefined) {
- data.status = dto.status;
- }
- try {
- return await this.mongoPrismaService.ads.update({
- where: { id: dto.id },
- data,
- include: { channel: true, adsModule: true },
- });
- } catch (e) {
- if (e instanceof PrismaClientKnownRequestError && e.code === 'P2025') {
- throw new NotFoundException('Ads not found');
- }
- throw e;
- }
- }
- async findOne(id: string) {
- const row = await this.mongoPrismaService.ads.findUnique({
- where: { id },
- include: { channel: true, adsModule: true },
- });
- if (!row) {
- throw new NotFoundException('Ads not found');
- }
- return row;
- }
- async list(dto: ListAdsDto) {
- const where: any = {};
- if (dto.title) {
- where.title = { contains: dto.title, mode: 'insensitive' };
- }
- if (dto.advertiser) {
- where.advertiser = { contains: dto.advertiser, mode: 'insensitive' };
- }
- if (dto.adsModuleId) {
- where.adsModuleId = dto.adsModuleId;
- }
- if (dto.channelId) {
- where.channelId = dto.channelId;
- }
- if (dto.status !== undefined) {
- where.status = dto.status;
- }
- // Defensive guards for pagination
- const page = dto.page && dto.page > 0 ? dto.page : 1;
- const size = dto.size && dto.size > 0 ? dto.size : 10;
- const [total, data] = await this.mongoPrismaService.$transaction([
- this.mongoPrismaService.ads.count({ where }),
- this.mongoPrismaService.ads.findMany({
- where,
- orderBy: { updateAt: 'desc' },
- skip: (page - 1) * size,
- take: size,
- include: {
- channel: true,
- adsModule: true,
- },
- }),
- ]);
- return {
- total,
- data,
- totalPages: Math.ceil(total / size),
- page,
- size,
- };
- }
- async remove(id: string) {
- try {
- await this.mongoPrismaService.ads.delete({ where: { id } });
- return { message: 'Deleted' };
- } catch (e) {
- if (e instanceof PrismaClientKnownRequestError && e.code === 'P2025') {
- throw new NotFoundException('Ads not found');
- }
- throw e;
- }
- }
- // get list of ads modules
- async listAdsModules() {
- const adsModules = await this.mongoPrismaService.adsModule.findMany({
- orderBy: { seq: 'asc' },
- });
- return adsModules;
- }
- }
|