Skip to content

YeboCars Services ​

Complete documentation of every service in the YeboCars API.

Service Overview ​

ServicePurposeKey Methods
CarServiceCar CRUD operationscreate, getCars, getById, update, delete
AIServiceAI search and recommendationssearchNaturalLanguage, getRecommendations
VINServiceVIN decodinglookupVIN, decodeModelYear
BillingServiceStripe subscriptionscreateCheckout, handleWebhook
DealerServiceDealer managementcreate, approve, getApplications
CountryServiceMulti-country supportgetCurrency, formatPrice
MessageServiceUser messagingsend, getConversations
NotificationServicePush notificationscreate, markRead

CarService ​

Core service for car listing management.

Key Methods ​

typescript
class CarService {
  // Create a new car listing
  static async createCar(
    carData: ICarInput, 
    sellerId: string, 
    sellerType: SellerType
  ): Promise<ICarResponse> {
    // 1. Validate seller (dealer must be approved, user must be verified)
    // 2. Determine country from seller's profile
    // 3. Create car record
    // 4. Return formatted response with currency
  }
  
  // Get cars with filters and pagination
  static async getCars(query: ICarQuery): Promise<{
    cars: ICarResponse[];
    pagination: IPagination;
  }> {
    const where = { status: 'ACTIVE' };
    
    // Apply filters
    if (query.make) where.make = { contains: query.make, mode: 'insensitive' };
    if (query.model) where.model = { contains: query.model, mode: 'insensitive' };
    if (query.minPrice) where.price = { gte: query.minPrice };
    if (query.maxPrice) where.price = { ...where.price, lte: query.maxPrice };
    if (query.fuelType) where.fuelType = query.fuelType;
    if (query.transmission) where.transmission = query.transmission;
    if (query.bodyType) where.bodyType = query.bodyType;
    if (query.countryId) where.countryId = query.countryId;
    
    const [cars, total] = await Promise.all([
      prisma.car.findMany({ where, skip, take: limit }),
      prisma.car.count({ where })
    ]);
    
    return { cars: formattedCars, pagination };
  }
  
  // Get single car by ID
  static async getCarById(carId: string): Promise<ICarResponse> {
    const car = await prisma.car.findUnique({ where: { id: carId } });
    const sellerData = await this.getSellerData(car.sellerId, car.sellerType);
    return this.formatCarResponse(car, sellerData, car.sellerType);
  }
  
  // Update car listing
  static async updateCar(
    carId: string, 
    carData: Partial<ICarInput>, 
    userId: string, 
    userRole: string
  ): Promise<ICarResponse> {
    // Verify ownership or admin role
    // Update car record
  }
  
  // Delete car listing
  static async deleteCar(carId: string, userId: string, userRole: string): Promise<void> {
    // Verify ownership or admin role
    // Delete car record
  }
  
  // Get available makes
  static async getMakes(): Promise<string[]> {
    const cars = await prisma.car.findMany({
      select: { make: true },
      distinct: ['make'],
      orderBy: { make: 'asc' }
    });
    return cars.map(c => c.make);
  }
  
  // Get models for a make
  static async getModelsByMake(make: string): Promise<string[]> {
    const cars = await prisma.car.findMany({
      where: { make: { equals: make, mode: 'insensitive' } },
      select: { model: true },
      distinct: ['model']
    });
    return cars.map(c => c.model);
  }
  
  // Get available body types
  static async getBodyTypes(): Promise<string[]> {
    return ['Sedan', 'SUV', 'Hatchback', 'Coupe', 'Convertible', 
            'Wagon', 'Truck', 'Van', 'Minivan', 'Crossover'];
  }
  
  // Get common features
  static async getFeatures(): Promise<string[]> {
    return ['Air Conditioning', 'Bluetooth', 'Backup Camera', 'Sunroof',
            'GPS Navigation', 'Heated Seats', 'Leather Seats', 'Keyless Entry',
            'Remote Start', 'Parking Sensors', 'Cruise Control', 'Power Windows'];
  }
}

Car Status Flow ​

PENDING ──► ACTIVE ──► SOLD
              │
              ▼
          INACTIVE
              │
              ▼
          REJECTED

Response Formatting ​

typescript
private static async formatCarResponse(car: any, seller: any, sellerType: SellerType): Promise<ICarResponse> {
  let currency, formattedPrice;
  
  if (car.countryId) {
    currency = await CountryService.getCountryCurrency(car.countryId);
    formattedPrice = await CountryService.formatPriceForCountry(car.price, car.countryId);
  }
  
  return {
    _id: car.id,
    id: car.id,
    make: car.make,
    model: car.model,
    year: car.year,
    price: car.price,
    mileage: car.mileage,
    fuelType: car.fuelType,
    transmission: car.transmission,
    bodyType: car.bodyType,
    color: car.color,
    exteriorColor: car.exteriorColor,
    interiorColor: car.interiorColor,
    description: car.description,
    images: car.images,
    videos: car.videos,
    features: car.features,
    sellerId: car.sellerId,
    sellerType: car.sellerType,
    sellerName: seller?.name,
    sellerPhone: seller?.phone,
    condition: car.condition,
    status: car.status,
    currency,
    formattedPrice,
    countryId: car.countryId,
    createdAt: car.createdAt,
    updatedAt: car.updatedAt
  };
}

AIService ​

AI-powered search and car recommendations.

typescript
class AIService {
  // Search cars using natural language
  static async searchNaturalLanguage(query: string): Promise<{
    models: any[];
    availableCars: ICarMatch[];
    searchIntent: any;
  }> {
    // 1. Parse query for structured filters
    const searchIntent = this.parseNaturalLanguageQuery(query);
    
    // 2. Build structured filters
    const structuredFilters = this.buildStructuredFilters(searchIntent);
    
    // 3. Search for matching cars
    const carFilter = { status: 'active' };
    
    if (searchIntent.make) carFilter.make = searchIntent.make;
    if (searchIntent.priceRange) {
      carFilter.price = { 
        gte: searchIntent.priceRange.min, 
        lte: searchIntent.priceRange.max 
      };
    }
    if (searchIntent.fuelType) carFilter.fuelType = searchIntent.fuelType;
    if (searchIntent.bodyType) carFilter.bodyType = searchIntent.bodyType;
    
    const availableCars = await Car.findMany({ where: carFilter }).limit(20);
    
    // 4. Calculate match scores
    const carMatches = availableCars.map(car => ({
      car: this.formatCarForMatch(car),
      matchScore: this.calculateMatchScore(car, searchIntent),
      reasons: this.generateMatchReasons(car, searchIntent),
      lifestyleMatch: this.generateLifestyleMatch(car, query)
    }));
    
    return {
      models: [],
      availableCars: carMatches,
      searchIntent: {
        query,
        extractedCriteria: searchIntent,
        confidence: this.calculateSearchConfidence(searchIntent)
      }
    };
  }
  
  // Parse natural language into structured query
  private static parseNaturalLanguageQuery(query: string): any {
    const terms = {};
    const lowerQuery = query.toLowerCase();
    
    // Extract make
    const makes = ['toyota', 'honda', 'bmw', 'mercedes', 'audi', 'volkswagen', 
                   'ford', 'nissan', 'mazda', 'subaru', 'lexus', 'tesla'];
    makes.forEach(make => {
      if (lowerQuery.includes(make)) {
        terms.make = make.charAt(0).toUpperCase() + make.slice(1);
      }
    });
    
    // Extract body type
    if (lowerQuery.includes('suv') || lowerQuery.includes('crossover')) terms.bodyType = 'suv';
    if (lowerQuery.includes('sedan')) terms.bodyType = 'sedan';
    if (lowerQuery.includes('truck') || lowerQuery.includes('pickup')) terms.bodyType = 'truck';
    
    // Extract fuel type
    if (lowerQuery.includes('electric') || lowerQuery.includes('ev')) terms.fuelType = 'Electric';
    if (lowerQuery.includes('hybrid')) terms.fuelType = 'Hybrid';
    if (lowerQuery.includes('diesel')) terms.fuelType = 'Diesel';
    
    // Extract price range
    const priceMatch = lowerQuery.match(/under\s*\$?([\d,]+)/i);
    if (priceMatch) {
      terms.priceRange = { min: 0, max: parseInt(priceMatch[1].replace(/,/g, '')) };
    }
    
    // Extract market segment
    if (lowerQuery.includes('cheap') || lowerQuery.includes('budget')) terms.marketSegment = 'economy';
    if (lowerQuery.includes('luxury') || lowerQuery.includes('premium')) terms.marketSegment = 'luxury';
    
    // Extract usage patterns
    if (lowerQuery.includes('family')) terms.targetDemographic = 'families';
    if (lowerQuery.includes('commut')) terms.targetDemographic = 'commuters';
    
    return terms;
  }
}

Lifestyle Recommendations ​

typescript
// Get recommendations based on lifestyle profile
static async getRecommendations(profile: ILifestyleProfile): Promise<ICarMatch[]> {
  const filter = { status: 'active' };
  
  // Budget matching
  if (profile.budget) {
    filter.price = { gte: profile.budget.min, lte: profile.budget.max };
  }
  
  // Fuel preference
  if (profile.fuelPreference) {
    filter.fuelType = profile.fuelPreference;
  }
  
  // Family size → body type
  if (profile.familySize > 4) {
    filter.bodyType = { in: ['SUV', 'MPV', 'Van'] };
  } else if (profile.familySize <= 2) {
    filter.bodyType = { in: ['Sedan', 'Hatchback', 'Coupe'] };
  }
  
  const cars = await Car.findMany({ where: filter }).limit(10);
  
  return cars.map(car => ({
    car: this.formatCarForMatch(car),
    matchScore: this.calculateLifestyleMatch(car, profile),
    reasons: this.generateLifestyleReasons(car, profile),
    lifestyleMatch: this.generateLifestyleDescription(car, profile)
  }));
}

// Get pricing insights
static async getPricingInsights(carId: string): Promise<IPricingInsights> {
  const car = await Car.findUnique({ where: { id: carId } });
  
  // Find comparable cars
  const comparables = await Car.find({
    make: car.make,
    model: car.model,
    year: { gte: car.year - 2, lte: car.year + 2 },
    status: 'active'
  }).limit(5);
  
  const averagePrice = comparables.reduce((sum, c) => sum + c.price, 0) / comparables.length;
  
  let marketPosition, suggestion;
  if (car.price > averagePrice * 1.1) {
    marketPosition = 'Above Market';
    suggestion = 'Consider reducing price';
  } else if (car.price < averagePrice * 0.9) {
    marketPosition = 'Below Market';
    suggestion = 'Price is competitive';
  } else {
    marketPosition = 'At Market';
    suggestion = 'Price is well-positioned';
  }
  
  return { marketPosition, suggestion, comparables };
}

VINService ​

Vehicle Identification Number decoding.

typescript
class VINService {
  static async lookupVIN(vin: string): Promise<IVINResponse> {
    // Validate VIN format
    if (!this.isValidVIN(vin)) {
      return { success: false, error: 'Invalid VIN format' };
    }
    
    // Decode VIN
    const vinData = this.decodeVIN(vin);
    
    return {
      success: true,
      data: vinData,
      source: 'VIN Service'
    };
  }
  
  private static isValidVIN(vin: string): boolean {
    // VIN must be 17 characters
    if (vin.length !== 17) return false;
    
    // VINs don't contain I, O, or Q
    if (/[IOQ]/.test(vin.toUpperCase())) return false;
    
    // Must be alphanumeric
    if (!/^[A-HJ-NPR-Z0-9]{17}$/i.test(vin)) return false;
    
    return true;
  }
  
  private static decodeModelYear(yearCode: string): number {
    const yearMap = {
      'A': 2010, 'B': 2011, 'C': 2012, 'D': 2013, 'E': 2014,
      'F': 2015, 'G': 2016, 'H': 2017, 'J': 2018, 'K': 2019,
      'L': 2020, 'M': 2021, 'N': 2022, 'P': 2023, 'R': 2024
    };
    return yearMap[yearCode.toUpperCase()] || new Date().getFullYear();
  }
  
  private static getManufacturerData(wmi: string): any {
    const manufacturers = {
      '1HG': { make: 'Honda', model: 'Civic', bodyType: 'Sedan' },
      '1FA': { make: 'Ford', model: 'Focus', bodyType: 'Hatchback' },
      '4T1': { make: 'Toyota', model: 'Camry', bodyType: 'Sedan' },
      'WBA': { make: 'BMW', model: '3 Series', bodyType: 'Sedan' },
      'JTD': { make: 'Toyota', model: 'RAV4', bodyType: 'SUV' }
    };
    return manufacturers[wmi] || { make: 'Unknown', model: 'Unknown' };
  }
}

BillingService ​

Stripe integration for dealer subscriptions.

typescript
class BillingService {
  private static PLANS = [
    {
      id: 'BASIC',
      name: 'Basic',
      priceUsdCents: 1000,  // $10/month
      description: '10 car listings, basic analytics',
      features: ['10 car listings', 'Basic analytics', 'Standard support'],
      listingLimit: 10,
      durationDays: 30
    },
    {
      id: 'DEALER',
      name: 'Dealer',
      priceUsdCents: 2500,  // $25/month
      description: '50 car listings, lead management',
      features: ['50 car listings', 'Lead management', 'Featured slots', 'Priority support'],
      listingLimit: 50,
      durationDays: 30
    },
    {
      id: 'PRO',
      name: 'Pro',
      priceUsdCents: 6000,  // $60/month
      description: 'Unlimited listings, priority placement',
      features: ['Unlimited listings', 'Priority placement', 'Advanced analytics', 'Account manager'],
      listingLimit: -1,
      durationDays: 30
    }
  ];
  
  // Get plans with localized pricing
  static getPlans(countryCode?: string) {
    const currency = getCurrencyForCountry(countryCode);
    
    return this.PLANS.map(plan => ({
      ...plan,
      displayCurrency: currency.code,
      displaySymbol: currency.symbol,
      displayAmount: convertFromUSD(plan.priceUsdCents, currency),
      displayFormatted: formatLocalAmount(amount, currency)
    }));
  }
  
  // Create Stripe checkout session
  static async createCheckout(opts: {
    dealerId: string;
    email: string;
    planId: string;
    successUrl: string;
    cancelUrl: string;
  }) {
    const plan = this.PLANS.find(p => p.id === opts.planId);
    
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      customer_email: opts.email,
      line_items: [{
        price_data: {
          currency: 'usd',
          product_data: { name: `YeboCars ${plan.name} Plan` },
          unit_amount: plan.priceUsdCents
        },
        quantity: 1
      }],
      mode: 'payment',
      success_url: opts.successUrl,
      cancel_url: opts.cancelUrl,
      metadata: { dealerId: opts.dealerId, planId: opts.planId }
    });
    
    return { sessionId: session.id, url: session.url };
  }
  
  // Handle Stripe webhook
  static async handleWebhookEvent(payload: Buffer, signature: string) {
    const event = stripe.webhooks.constructEvent(
      payload, signature, process.env.STRIPE_WEBHOOK_SECRET
    );
    
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;
      const { dealerId, planId } = session.metadata;
      
      const plan = this.PLANS.find(p => p.id === planId);
      const expiry = new Date();
      expiry.setDate(expiry.getDate() + plan.durationDays);
      
      await prisma.dealer.update({
        where: { id: dealerId },
        data: {
          plan: planId,
          planExpiry: expiry,
          stripeCustomerId: session.customer
        }
      });
    }
    
    return { received: true, type: event.type };
  }
}

CountryService ​

Multi-country currency and formatting support.

typescript
class CountryService {
  // Get currency for a country
  static async getCountryCurrency(countryId: string): Promise<Currency> {
    const country = await prisma.country.findUnique({
      where: { id: countryId },
      select: { currency: true }
    });
    return country?.currency;
  }
  
  // Format price for country
  static async formatPriceForCountry(price: number, countryId: string): Promise<string> {
    const currency = await this.getCountryCurrency(countryId);
    return `${currency.symbol}${price.toLocaleString()}`;
  }
}

One chat. Everything done.