|
@@ -4,6 +4,7 @@ import {
|
|
|
ImageSource,
|
|
ImageSource,
|
|
|
Prisma,
|
|
Prisma,
|
|
|
} from '@prisma/mongo/client';
|
|
} from '@prisma/mongo/client';
|
|
|
|
|
+
|
|
|
export function nowSecBigInt(): bigint {
|
|
export function nowSecBigInt(): bigint {
|
|
|
return BigInt(Math.floor(Date.now() / 1000));
|
|
return BigInt(Math.floor(Date.now() / 1000));
|
|
|
}
|
|
}
|
|
@@ -13,6 +14,7 @@ const prisma = new PrismaClient();
|
|
|
type SeedArgs = {
|
|
type SeedArgs = {
|
|
|
clean: boolean;
|
|
clean: boolean;
|
|
|
perType: number;
|
|
perType: number;
|
|
|
|
|
+ adIdStart: number;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
function parseArgs(argv: string[]): SeedArgs {
|
|
function parseArgs(argv: string[]): SeedArgs {
|
|
@@ -22,7 +24,7 @@ function parseArgs(argv: string[]): SeedArgs {
|
|
|
process.env.SEED_CLEAN === '1' ||
|
|
process.env.SEED_CLEAN === '1' ||
|
|
|
process.env.SEED_CLEAN === 'true';
|
|
process.env.SEED_CLEAN === 'true';
|
|
|
|
|
|
|
|
- const perTypeFromArg = (() => {
|
|
|
|
|
|
|
+ const perType = (() => {
|
|
|
const idx = argv.findIndex((a) => a === '--perType');
|
|
const idx = argv.findIndex((a) => a === '--perType');
|
|
|
if (idx >= 0) {
|
|
if (idx >= 0) {
|
|
|
const v = Number(argv[idx + 1]);
|
|
const v = Number(argv[idx + 1]);
|
|
@@ -33,7 +35,18 @@ function parseArgs(argv: string[]): SeedArgs {
|
|
|
return 100;
|
|
return 100;
|
|
|
})();
|
|
})();
|
|
|
|
|
|
|
|
- return { clean, perType: perTypeFromArg };
|
|
|
|
|
|
|
+ const adIdStart = (() => {
|
|
|
|
|
+ const idx = argv.findIndex((a) => a === '--adIdStart');
|
|
|
|
|
+ if (idx >= 0) {
|
|
|
|
|
+ const v = Number(argv[idx + 1]);
|
|
|
|
|
+ if (Number.isFinite(v) && v > 0) return Math.floor(v);
|
|
|
|
|
+ }
|
|
|
|
|
+ const envValue = Number(process.env.SEED_ADID_START);
|
|
|
|
|
+ if (Number.isFinite(envValue) && envValue > 0) return Math.floor(envValue);
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ return { clean, perType, adIdStart };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function nowEpochSec(): bigint {
|
|
function nowEpochSec(): bigint {
|
|
@@ -96,6 +109,39 @@ function makeTimeWindow(): { startDt: bigint; expiryDt: bigint } {
|
|
|
return { startDt: BigInt(start), expiryDt: BigInt(expiry) };
|
|
return { startDt: BigInt(start), expiryDt: BigInt(expiry) };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Align the Mongo "counters" collection with the next adId we plan to use.
|
|
|
|
|
+ * This ensures future runtime allocator (findAndModify $inc) won't collide.
|
|
|
|
|
+ *
|
|
|
|
|
+ * We set counters.seq = maxExistingAdId (or adIdStart-1 if empty),
|
|
|
|
|
+ * so the next allocate will return max+1.
|
|
|
|
|
+ */
|
|
|
|
|
+async function ensureAdIdCounterAligned(adIdStart: number) {
|
|
|
|
|
+ const maxRow = await prisma.ads.findFirst({
|
|
|
|
|
+ where: { adId: { not: null } },
|
|
|
|
|
+ select: { adId: true },
|
|
|
|
|
+ orderBy: { adId: 'desc' as any }, // Prisma Mongo sometimes needs 'as any' for orderBy on optional field
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const maxExisting =
|
|
|
|
|
+ typeof maxRow?.adId === 'number' ? maxRow.adId : undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const base = Math.max((adIdStart ?? 1) - 1, maxExisting ?? 0);
|
|
|
|
|
+
|
|
|
|
|
+ await prisma.$runCommandRaw({
|
|
|
|
|
+ update: 'counters',
|
|
|
|
|
+ updates: [
|
|
|
|
|
+ {
|
|
|
|
|
+ q: { _id: 'ads_adId' },
|
|
|
|
|
+ u: { $set: { seq: base } },
|
|
|
|
|
+ upsert: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`[seed:ads] counters.ads_adId aligned: seq=${base}`);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function main() {
|
|
async function main() {
|
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const args = parseArgs(process.argv.slice(2));
|
|
|
const tsNow = nowEpochSec();
|
|
const tsNow = nowEpochSec();
|
|
@@ -106,6 +152,9 @@ async function main() {
|
|
|
console.log('[seed:ads] cleaned ads collection');
|
|
console.log('[seed:ads] cleaned ads collection');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Important: If your app uses the atomic allocator, align the counter to avoid collisions.
|
|
|
|
|
+ await ensureAdIdCounterAligned(args.adIdStart);
|
|
|
|
|
+
|
|
|
const advertiserPrefixes = [
|
|
const advertiserPrefixes = [
|
|
|
'广告商A',
|
|
'广告商A',
|
|
|
'广告商B',
|
|
'广告商B',
|
|
@@ -126,6 +175,8 @@ async function main() {
|
|
|
|
|
|
|
|
const batch: Prisma.AdsCreateManyInput[] = [];
|
|
const batch: Prisma.AdsCreateManyInput[] = [];
|
|
|
|
|
|
|
|
|
|
+ let nextAdId = args.adIdStart;
|
|
|
|
|
+
|
|
|
for (const adType of adTypes) {
|
|
for (const adType of adTypes) {
|
|
|
for (let idx = 0; idx < args.perType; idx++) {
|
|
for (let idx = 0; idx < args.perType; idx++) {
|
|
|
const { startDt, expiryDt } = makeTimeWindow();
|
|
const { startDt, expiryDt } = makeTimeWindow();
|
|
@@ -137,6 +188,7 @@ async function main() {
|
|
|
const adsContent = Math.random() < 0.8 ? makeContent(adType) : null;
|
|
const adsContent = Math.random() < 0.8 ? makeContent(adType) : null;
|
|
|
|
|
|
|
|
batch.push({
|
|
batch.push({
|
|
|
|
|
+ adId: nextAdId++, // ✅ NEW
|
|
|
adType,
|
|
adType,
|
|
|
advertiser,
|
|
advertiser,
|
|
|
title,
|
|
title,
|
|
@@ -156,7 +208,7 @@ async function main() {
|
|
|
|
|
|
|
|
const result = await prisma.ads.createMany({ data: batch });
|
|
const result = await prisma.ads.createMany({ data: batch });
|
|
|
console.log(
|
|
console.log(
|
|
|
- `[seed:ads] adTypes=${adTypes.length} perType=${args.perType} inserted=${result.count} clean=${args.clean}`,
|
|
|
|
|
|
|
+ `[seed:ads] adTypes=${adTypes.length} perType=${args.perType} inserted=${result.count} clean=${args.clean} adIdStart=${args.adIdStart}`,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -168,3 +220,13 @@ main()
|
|
|
.finally(async () => {
|
|
.finally(async () => {
|
|
|
await prisma.$disconnect();
|
|
await prisma.$disconnect();
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Usage:
|
|
|
|
|
+ * pnpm ts-node ./path/to/seed-ads.ts --clean --perType 50 --adIdStart 1
|
|
|
|
|
+ *
|
|
|
|
|
+ * Env:
|
|
|
|
|
+ * SEED_CLEAN=true
|
|
|
|
|
+ * SEED_PER_TYPE=100
|
|
|
|
|
+ * SEED_ADID_START=1
|
|
|
|
|
+ */
|