All files / src/modules/boost boost.service.ts

23.25% Statements 10/43
50% Branches 9/18
0% Functions 0/5
21.62% Lines 8/37

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 10211x 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;
  }
}