|
|
@@ -33,6 +33,7 @@ import { nowSecBigInt, toSecBigInt } from '@box/common/time/time.util';
|
|
|
@Injectable()
|
|
|
export class AdsService {
|
|
|
private readonly logger = new Logger(AdsService.name);
|
|
|
+ private readonly ADS_ADID_COUNTER_KEY = 'ads_adId';
|
|
|
|
|
|
constructor(
|
|
|
private readonly mongoPrismaService: MongoPrismaService,
|
|
|
@@ -101,6 +102,27 @@ export class AdsService {
|
|
|
return adsModule.adType;
|
|
|
}
|
|
|
|
|
|
+ private async nextCounterSeq(counterId: string): Promise<number> {
|
|
|
+ // Mongo command: findAndModify on "counters" collection
|
|
|
+ const res = (await this.mongoPrismaService.$runCommandRaw({
|
|
|
+ findAndModify: 'counters',
|
|
|
+ query: { _id: counterId },
|
|
|
+ update: { $inc: { seq: 1 } },
|
|
|
+ upsert: true,
|
|
|
+ new: true,
|
|
|
+ })) as any;
|
|
|
+
|
|
|
+ const seq = res?.value?.seq;
|
|
|
+ if (typeof seq !== 'number') {
|
|
|
+ throw new Error(`Failed to allocate counter seq for ${counterId}`);
|
|
|
+ }
|
|
|
+ return seq;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async allocateAdId(): Promise<number> {
|
|
|
+ return this.nextCounterSeq(this.ADS_ADID_COUNTER_KEY);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Ensure the channel exists.
|
|
|
*/
|
|
|
@@ -136,6 +158,37 @@ export class AdsService {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ async backfillAdIds(batchSize = 200): Promise<{ updated: number }> {
|
|
|
+ let updated = 0;
|
|
|
+ let cursorId: string | undefined = undefined;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ const rows = await this.mongoPrismaService.ads.findMany({
|
|
|
+ where: {
|
|
|
+ adId: null,
|
|
|
+ ...(cursorId ? { id: { gt: cursorId } } : {}),
|
|
|
+ },
|
|
|
+ select: { id: true },
|
|
|
+ orderBy: { id: 'asc' },
|
|
|
+ take: batchSize,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (rows.length === 0) break;
|
|
|
+
|
|
|
+ for (const r of rows) {
|
|
|
+ const adId = await this.allocateAdId();
|
|
|
+ await this.mongoPrismaService.ads.update({
|
|
|
+ where: { id: r.id },
|
|
|
+ data: { adId },
|
|
|
+ });
|
|
|
+ updated++;
|
|
|
+ cursorId = r.id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { updated };
|
|
|
+ }
|
|
|
+
|
|
|
async create(dto: CreateAdsDto) {
|
|
|
this.ensureTimeRange(dto.startDt, dto.expiryDt);
|
|
|
|
|
|
@@ -153,8 +206,10 @@ export class AdsService {
|
|
|
}
|
|
|
|
|
|
const now = this.nowSeconds();
|
|
|
+ const adId = await this.allocateAdId();
|
|
|
|
|
|
const adData: any = {
|
|
|
+ adId,
|
|
|
adType,
|
|
|
advertiser: this.trimOptional(dto.advertiser),
|
|
|
title: this.trimOptional(dto.title),
|