Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 11x 11x 11x 11x 11x 11x 11x 11x | import { Injectable, ForbiddenException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@app/modules/prisma/prisma.service';
import { PaymentsService } from '@app/modules/boost/payments.service';
import { BoostTier } from '@prisma/client';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
// Duration in days for each tier
const BOOST_DURATION_DAYS: Record<BoostTier, number> = {
STANDARD: 7,
PREMIUM: 14,
DIAMOND: 30,
};
// Mock pricing table USD
const BOOST_PRICES: Record<BoostTier, number> = {
STANDARD: 9.99,
PREMIUM: 19.99,
DIAMOND: 39.99,
};
@Injectable()
export class BoostService {
constructor(
private readonly prisma: PrismaService,
private readonly payments: PaymentsService,
@InjectQueue('listingIndex') private readonly indexQueue: Queue,
) {}
async createSession(listingId: number, tier: BoostTier, userId: number) {
// Validate listing ownership or agent role
const listing = await this.prisma.client.listing.findUnique({
where: { id: listingId },
select: { userId: true },
});
Iif (!listing) {
throw new NotFoundException('Listing not found');
}
Iif (listing.userId !== userId) {
throw new ForbiddenException('You do not own this listing');
}
// Ensure no active boost exists
const activeBoost = await this.prisma.client.boost.findFirst({
where: { listingId, active: true },
});
Iif (activeBoost) {
throw new ForbiddenException('Listing already has an active boost');
}
const now = new Date();
const endAt = new Date(now.getTime() + BOOST_DURATION_DAYS[tier] * 24 * 60 * 60 * 1000);
const price = BOOST_PRICES[tier];
const boost = await this.prisma.client.boost.create({
data: {
listingId,
tier,
price,
startAt: now,
endAt,
active: false,
},
});
const checkoutUrl = this.payments.createCheckoutUrl();
return { boostId: boost.id, checkoutUrl };
}
async handleWebhook(listingId: number, success: boolean) {
Iif (!success) return;
const boost = await this.prisma.client.boost.findFirst({
where: { listingId, active: false },
orderBy: { createdAt: 'desc' },
});
Iif (!boost) return;
await this.prisma.client.boost.update({ where: { id: boost.id }, data: { active: true } });
await this.indexQueue.add('index', { listingId });
}
async expireBoosts(): Promise<number> {
const expiredBoosts = await this.prisma.client.boost.findMany({
where: { active: true, endAt: { lt: new Date() } },
select: { id: true, listingId: true },
});
Iif (expiredBoosts.length === 0) return 0;
const ids = expiredBoosts.map((b: { id: number }) => b.id);
await this.prisma.client.boost.updateMany({ where: { id: { in: ids } }, data: { active: false } });
// Reindex affected listings
for (const b of expiredBoosts) {
await this.indexQueue.add('index', { listingId: b.listingId });
}
return expiredBoosts.length;
}
} |